Advertisement
Guest User

Untitled

a guest
Mar 8th, 2020
642
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 204.33 KB | None | 0 0
  1. -- This module holds any type of chatting functions
  2. CATEGORY_NAME = "Chat"
  3.  
  4. ------------------------------ Psay ------------------------------
  5. function ulx.psay( calling_ply, target_ply, message )
  6.     if calling_ply:GetNWBool( "ulx_muted", false ) then
  7.         ULib.tsayError( calling_ply, "You are muted, and therefore cannot speak! Use asay for admin chat if urgent.", true )
  8.         return
  9.     end
  10.  
  11.     ulx.fancyLog( { target_ply, calling_ply }, "#P to #P: " .. message, calling_ply, target_ply )
  12. end
  13. local psay = ulx.command( CATEGORY_NAME, "ulx psay", ulx.psay, "!p", true )
  14. psay:addParam{ type=ULib.cmds.PlayerArg, target="!^", ULib.cmds.ignoreCanTarget }
  15. psay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
  16. psay:defaultAccess( ULib.ACCESS_ALL )
  17. psay:help( "Send a private message to target." )
  18.  
  19. ------------------------------ Asay ------------------------------
  20. local seeasayAccess = "ulx seeasay"
  21. if SERVER then ULib.ucl.registerAccess( seeasayAccess, ULib.ACCESS_OPERATOR, "Ability to see 'ulx asay'", "Other" ) end -- Give operators access to see asays echoes by default
  22.  
  23. function ulx.asay( calling_ply, message )
  24.     local format
  25.     local me = "/me "
  26.     if message:sub( 1, me:len() ) == me then
  27.         format = "(ADMINS) *** #P #s"
  28.         message = message:sub( me:len() + 1 )
  29.     else
  30.         format = "#P to admins: #s"
  31.     end
  32.  
  33.     local players = player.GetAll()
  34.     for i=#players, 1, -1 do
  35.         local v = players[ i ]
  36.         if not ULib.ucl.query( v, seeasayAccess ) and v ~= calling_ply then -- Calling player always gets to see the echo
  37.             table.remove( players, i )
  38.         end
  39.     end
  40.  
  41.     ulx.fancyLog( players, format, calling_ply, message )
  42. end
  43. local asay = ulx.command( CATEGORY_NAME, "ulx asay", ulx.asay, "@", true, true )
  44. asay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
  45. asay:defaultAccess( ULib.ACCESS_ALL )
  46. asay:help( "Send a message to currently connected admins." )
  47.  
  48. ------------------------------ Tsay ------------------------------
  49. function ulx.tsay( calling_ply, message )
  50.     ULib.tsay( _, message )
  51.  
  52.     if ULib.toBool( GetConVarNumber( "ulx_logChat" ) ) then
  53.         ulx.logString( string.format( "(tsay from %s) %s", calling_ply:IsValid() and calling_ply:Nick() or "Console", message ) )
  54.     end
  55. end
  56. local tsay = ulx.command( CATEGORY_NAME, "ulx tsay", ulx.tsay, "@@", true, true )
  57. tsay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
  58. tsay:defaultAccess( ULib.ACCESS_ADMIN )
  59. tsay:help( "Send a message to everyone in the chat box." )
  60.  
  61. ------------------------------ Csay ------------------------------
  62. function ulx.csay( calling_ply, message )
  63.     ULib.csay( _, message )
  64.  
  65.     if ULib.toBool( GetConVarNumber( "ulx_logChat" ) ) then
  66.         ulx.logString( string.format( "(csay from %s) %s", calling_ply:IsValid() and calling_ply:Nick() or "Console", message ) )
  67.     end
  68. end
  69. local csay = ulx.command( CATEGORY_NAME, "ulx csay", ulx.csay, "@@@", true, true )
  70. csay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
  71. csay:defaultAccess( ULib.ACCESS_ADMIN )
  72. csay:help( "Send a message to everyone in the middle of their screen." )
  73.  
  74. ------------------------------ Thetime ------------------------------
  75. local waittime = 60
  76. local lasttimeusage = -waittime
  77. function ulx.thetime( calling_ply )
  78.     if lasttimeusage + waittime > CurTime() then
  79.         ULib.tsayError( calling_ply, "I just told you what time it is! Please wait " .. waittime .. " seconds before using this command again", true )
  80.         return
  81.     end
  82.  
  83.     lasttimeusage = CurTime()
  84.     ulx.fancyLog( "The time is now #s.", os.date( "%I:%M %p") )
  85. end
  86. local thetime = ulx.command( CATEGORY_NAME, "ulx thetime", ulx.thetime, "!thetime" )
  87. thetime:defaultAccess( ULib.ACCESS_ALL )
  88. thetime:help( "Shows you the server time." )
  89.  
  90.  
  91. ------------------------------ Adverts ------------------------------
  92. ulx.adverts = ulx.adverts or {}
  93. local adverts = ulx.adverts -- For XGUI, too lazy to change all refs
  94.  
  95. local function doAdvert( group, id )
  96.  
  97.     if adverts[ group ][ id ] == nil then
  98.         if adverts[ group ].removed_last then
  99.             adverts[ group ].removed_last = nil
  100.             id = 1
  101.         else
  102.             id = #adverts[ group ]
  103.         end
  104.     end
  105.  
  106.     local info = adverts[ group ][ id ]
  107.  
  108.     local message = string.gsub( info.message, "%%curmap%%", game.GetMap() )
  109.     message = string.gsub( message, "%%host%%", GetConVarString( "hostname" ) )
  110.     message = string.gsub( message, "%%ulx_version%%", ULib.pluginVersionStr( "ULX" ) )
  111.  
  112.     if not info.len then -- tsay
  113.         local lines = ULib.explode( "\\n", message )
  114.  
  115.         for i, line in ipairs( lines ) do
  116.             local trimmed = line:Trim()
  117.             if trimmed:len() > 0 then
  118.                 ULib.tsayColor( _, true, info.color, trimmed ) -- Delaying runs one message every frame (to ensure correct order)
  119.             end
  120.         end
  121.     else
  122.         ULib.csay( _, message, info.color, info.len )
  123.     end
  124.  
  125.     ULib.queueFunctionCall( function()
  126.         local nextid = math.fmod( id, #adverts[ group ] ) + 1
  127.         timer.Remove( "ULXAdvert" .. type( group ) .. group )
  128.         timer.Create( "ULXAdvert" .. type( group ) .. group, adverts[ group ][ nextid ].rpt, 1, function() doAdvert( group, nextid ) end )
  129.     end )
  130. end
  131.  
  132. -- Whether or not it's a csay is determined by whether there's a value specified in "len"
  133. function ulx.addAdvert( message, rpt, group, color, len )
  134.     local t
  135.  
  136.     if group then
  137.         t = adverts[ tostring( group ) ]
  138.         if not t then
  139.             t = {}
  140.             adverts[ tostring( group ) ] = t
  141.         end
  142.     else
  143.         group = table.insert( adverts, {} )
  144.         t = adverts[ group ]
  145.     end
  146.  
  147.     local id = table.insert( t, { message=message, rpt=rpt, color=color, len=len } )
  148.  
  149.     if not timer.Exists( "ULXAdvert" .. type( group ) .. group ) then
  150.         timer.Create( "ULXAdvert" .. type( group ) .. group, rpt, 1, function() doAdvert( group, id ) end )
  151.     end
  152. end
  153.  
  154. ------------------------------ Gimp ------------------------------
  155. ulx.gimpSays = ulx.gimpSays or {} -- Holds gimp says
  156. local gimpSays = ulx.gimpSays -- For XGUI, too lazy to change all refs
  157. local ID_GIMP = 1
  158. local ID_MUTE = 2
  159.  
  160. function ulx.addGimpSay( say )
  161.     table.insert( gimpSays, say )
  162. end
  163.  
  164. function ulx.clearGimpSays()
  165.     table.Empty( gimpSays )
  166. end
  167.  
  168. function ulx.gimp( calling_ply, target_plys, should_ungimp )
  169.     for i=1, #target_plys do
  170.         local v = target_plys[ i ]
  171.         if should_ungimp then
  172.             v.gimp = nil
  173.         else
  174.             v.gimp = ID_GIMP
  175.         end
  176.         v:SetNWBool("ulx_gimped", not should_ungimp)
  177.     end
  178.  
  179.     if not should_ungimp then
  180.         ulx.fancyLogAdmin( calling_ply, "#A gimped #T", target_plys )
  181.     else
  182.         ulx.fancyLogAdmin( calling_ply, "#A ungimped #T", target_plys )
  183.     end
  184. end
  185. local gimp = ulx.command( CATEGORY_NAME, "ulx gimp", ulx.gimp, "!gimp" )
  186. gimp:addParam{ type=ULib.cmds.PlayersArg }
  187. gimp:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  188. gimp:defaultAccess( ULib.ACCESS_ADMIN )
  189. gimp:help( "Gimps target(s) so they are unable to chat normally." )
  190. gimp:setOpposite( "ulx ungimp", {_, _, true}, "!ungimp" )
  191.  
  192. ------------------------------ Mute ------------------------------
  193. function ulx.mute( calling_ply, target_plys, should_unmute )
  194.     for i=1, #target_plys do
  195.         local v = target_plys[ i ]
  196.         if should_unmute then
  197.             v.gimp = nil
  198.         else
  199.             v.gimp = ID_MUTE
  200.         end
  201.         v:SetNWBool("ulx_muted", not should_unmute)
  202.     end
  203.  
  204.     if not should_unmute then
  205.         ulx.fancyLogAdmin( calling_ply, "#A muted #T", target_plys )
  206.     else
  207.         ulx.fancyLogAdmin( calling_ply, "#A unmuted #T", target_plys )
  208.     end
  209. end
  210. local mute = ulx.command( CATEGORY_NAME, "ulx mute", ulx.mute, "!mute" )
  211. mute:addParam{ type=ULib.cmds.PlayersArg }
  212. mute:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  213. mute:defaultAccess( ULib.ACCESS_ADMIN )
  214. mute:help( "Mutes target(s) so they are unable to chat." )
  215. mute:setOpposite( "ulx unmute", {_, _, true}, "!unmute" )
  216.  
  217. if SERVER then
  218.     local function gimpCheck( ply, strText )
  219.         if ply.gimp == ID_MUTE then return "" end
  220.         if ply.gimp == ID_GIMP then
  221.             if #gimpSays < 1 then return nil end
  222.             return gimpSays[ math.random( #gimpSays ) ]
  223.         end
  224.     end
  225.     hook.Add( "PlayerSay", "ULXGimpCheck", gimpCheck, HOOK_LOW )
  226. end
  227.  
  228. ------------------------------ Gag ------------------------------
  229. function ulx.gag( calling_ply, target_plys, should_ungag )
  230.     local players = player.GetAll()
  231.     for i=1, #target_plys do
  232.         local v = target_plys[ i ]
  233.         v.ulx_gagged = not should_ungag
  234.         v:SetNWBool("ulx_gagged", v.ulx_gagged)
  235.     end
  236.  
  237.     if not should_ungag then
  238.         ulx.fancyLogAdmin( calling_ply, "#A gagged #T", target_plys )
  239.     else
  240.         ulx.fancyLogAdmin( calling_ply, "#A ungagged #T", target_plys )
  241.     end
  242. end
  243. local gag = ulx.command( CATEGORY_NAME, "ulx gag", ulx.gag, "!gag" )
  244. gag:addParam{ type=ULib.cmds.PlayersArg }
  245. gag:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  246. gag:defaultAccess( ULib.ACCESS_ADMIN )
  247. gag:help( "Gag target(s), disables microphone." )
  248. gag:setOpposite( "ulx ungag", {_, _, true}, "!ungag" )
  249.  
  250. local function gagHook( listener, talker )
  251.     if talker.ulx_gagged then
  252.         return false
  253.     end
  254. end
  255. hook.Add( "PlayerCanHearPlayersVoice", "ULXGag", gagHook )
  256.  
  257. -- Anti-spam stuff
  258. if SERVER then
  259.     local chattime_cvar = ulx.convar( "chattime", "1.5", "<time> - Players can only chat every x seconds (anti-spam). 0 to disable.", ULib.ACCESS_ADMIN )
  260.     local function playerSay( ply )
  261.         if not ply.lastChatTime then ply.lastChatTime = 0 end
  262.  
  263.         local chattime = chattime_cvar:GetFloat()
  264.         if chattime <= 0 then return end
  265.  
  266.         if ply.lastChatTime + chattime > CurTime() then
  267.             return ""
  268.         else
  269.             ply.lastChatTime = CurTime()
  270.             return
  271.         end
  272.     end
  273.     hook.Add( "PlayerSay", "ulxPlayerSay", playerSay, HOOK_LOW )
  274.  
  275.     local function meCheck( ply, strText, bTeam )
  276.         local meChatEnabled = GetConVarNumber( "ulx_meChatEnabled" )
  277.  
  278.         if ply.gimp or meChatEnabled == 0 or (meChatEnabled ~= 2 and GAMEMODE.Name ~= "Sandbox") then return end -- Don't mess
  279.  
  280.         if strText:sub( 1, 4 ) == "/me " then
  281.             strText = string.format( "*** %s %s", ply:Nick(), strText:sub( 5 ) )
  282.             if not bTeam then
  283.                 ULib.tsay( _, strText )
  284.             else
  285.                 strText = "(TEAM) " .. strText
  286.                 local teamid = ply:Team()
  287.                 local players = team.GetPlayers( teamid )
  288.                 for _, ply2 in ipairs( players ) do
  289.                     ULib.tsay( ply2, strText )
  290.                 end
  291.             end
  292.  
  293.             if game.IsDedicated() then
  294.                 Msg( strText .. "\n" ) -- Log to console
  295.             end
  296.             if ULib.toBool( GetConVarNumber( "ulx_logChat" ) ) then
  297.                 ulx.logString( strText )
  298.             end
  299.  
  300.             return ""
  301.         end
  302.  
  303.     end
  304.     hook.Add( "PlayerSay", "ULXMeCheck", meCheck, HOOK_LOW ) -- Extremely low priority
  305. end
  306.  
  307. local function showWelcome( ply )
  308.     local message = GetConVarString( "ulx_welcomemessage" )
  309.     if not message or message == "" then return end
  310.  
  311.     message = string.gsub( message, "%%curmap%%", game.GetMap() )
  312.     message = string.gsub( message, "%%host%%", GetConVarString( "hostname" ) )
  313.     message = string.gsub( message, "%%ulx_version%%", ULib.pluginVersionStr( "ULX" ) )
  314.  
  315.     ply:ChatPrint( message ) -- We're not using tsay because ULib might not be loaded yet. (client side)
  316. end
  317. hook.Add( "PlayerInitialSpawn", "ULXWelcome", showWelcome )
  318. if SERVER then
  319.     ulx.convar( "meChatEnabled", "1", "Allow players to use '/me' in chat. 0 = Disabled, 1 = Sandbox only (Default), 2 = Enabled", ULib.ACCESS_ADMIN )
  320.     ulx.convar( "welcomemessage", "", "<msg> - This is shown to players on join.", ULib.ACCESS_ADMIN )
  321. end
  322. local CATEGORY_NAME = "Fun"
  323.  
  324. ------------------------------ Slap ------------------------------
  325. function ulx.slap( calling_ply, target_plys, dmg )
  326.     local affected_plys = {}
  327.  
  328.     for i=1, #target_plys do
  329.         local v = target_plys[ i ]
  330.         if v:IsFrozen() then
  331.             ULib.tsayError( calling_ply, v:Nick() .. " is frozen!", true )
  332.         else
  333.             ULib.slap( v, dmg )
  334.             table.insert( affected_plys, v )
  335.         end
  336.     end
  337.  
  338.     ulx.fancyLogAdmin( calling_ply, "#A slapped #T with #i damage", affected_plys, dmg )
  339. end
  340.  
  341. local slap = ulx.command( CATEGORY_NAME, "ulx slap", ulx.slap, "!slap" )
  342. slap:addParam{ type=ULib.cmds.PlayersArg }
  343. slap:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="damage", ULib.cmds.optional, ULib.cmds.round }
  344. slap:defaultAccess( ULib.ACCESS_ADMIN )
  345. slap:help( "Slaps target(s) with given damage." )
  346.  
  347. ------------------------------ Whip ------------------------------
  348. function ulx.whip( calling_ply, target_plys, times, dmg )
  349.     local affected_plys = {}
  350.  
  351.     for i=1, #target_plys do
  352.         local v = target_plys[ i ]
  353.  
  354.         if v.whipped then
  355.             ULib.tsayError( calling_ply, v:Nick() .. " is already being whipped by " .. v.whippedby, true )
  356.         elseif v:IsFrozen() then
  357.             ULib.tsayError( calling_ply, v:Nick() .. " is frozen!", true )
  358.         else
  359.             local dtime = 0
  360.             v.whipped = true
  361.             v.whippedby = calling_ply:IsValid() and calling_ply:Nick() or "(Console)"
  362.             v.whipcount = 0
  363.             v.whipamt = times
  364.  
  365.             timer.Create( "ulxWhip" .. v:EntIndex(), 0.5, 0, function() -- Repeat forever, we have an unhooker inside.
  366.                 if not v:IsValid() then timer.Remove( "ulxWhip" .. v:EntIndex() ) return end  -- Gotta make sure they're still there since this is a timer.
  367.                 if v.whipcount == v.whipamt or not v:Alive() then
  368.                     v.whipped = nil
  369.                     v.whippedby = nil
  370.                     v.whipcount = nil
  371.                     v.whipamt = nil
  372.                     timer.Remove( "ulxWhip" .. v:EntIndex() )
  373.                 else
  374.                     ULib.slap( v, dmg )
  375.                     v.whipcount = v.whipcount + 1
  376.                 end
  377.             end )
  378.  
  379.             table.insert( affected_plys, v )
  380.         end
  381.     end
  382.  
  383.     ulx.fancyLogAdmin( calling_ply, "#A whipped #T #i times with #i damage", affected_plys, times, dmg )
  384. end
  385. local whip = ulx.command( CATEGORY_NAME, "ulx whip", ulx.whip, "!whip" )
  386. whip:addParam{ type=ULib.cmds.PlayersArg }
  387. whip:addParam{ type=ULib.cmds.NumArg, min=2, max=100, default=10, hint="times", ULib.cmds.optional, ULib.cmds.round }
  388. whip:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="damage", ULib.cmds.optional, ULib.cmds.round }
  389. whip:defaultAccess( ULib.ACCESS_ADMIN )
  390. whip:help( "Slaps target(s) x times with given damage each time." )
  391.  
  392. ------------------------------ Slay ------------------------------
  393. function ulx.slay( calling_ply, target_plys )
  394.     local affected_plys = {}
  395.  
  396.     for i=1, #target_plys do
  397.         local v = target_plys[ i ]
  398.  
  399.         if ulx.getExclusive( v, calling_ply ) then
  400.             ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  401.         elseif not v:Alive() then
  402.             ULib.tsayError( calling_ply, v:Nick() .. " is already dead!", true )
  403.         elseif v:IsFrozen() then
  404.             ULib.tsayError( calling_ply, v:Nick() .. " is frozen!", true )
  405.         else
  406.             v:Kill()
  407.             table.insert( affected_plys, v )
  408.         end
  409.     end
  410.  
  411.     ulx.fancyLogAdmin( calling_ply, "#A slayed #T", affected_plys )
  412. end
  413. local slay = ulx.command( CATEGORY_NAME, "ulx slay", ulx.slay, "!slay" )
  414. slay:addParam{ type=ULib.cmds.PlayersArg }
  415. slay:defaultAccess( ULib.ACCESS_ADMIN )
  416. slay:help( "Slays target(s)." )
  417.  
  418. ------------------------------ Sslay ------------------------------
  419. function ulx.sslay( calling_ply, target_plys )
  420.     local affected_plys = {}
  421.  
  422.     for i=1, #target_plys do
  423.         local v = target_plys[ i ]
  424.  
  425.         if ulx.getExclusive( v, calling_ply ) then
  426.             ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  427.         elseif not v:Alive() then
  428.             ULib.tsayError( calling_ply, v:Nick() .. " is already dead!", true )
  429.         elseif v:IsFrozen() then
  430.             ULib.tsayError( calling_ply, v:Nick() .. " is frozen!", true )
  431.         else
  432.             if v:InVehicle() then
  433.                 v:ExitVehicle()
  434.             end
  435.  
  436.             v:KillSilent()
  437.             table.insert( affected_plys, v )
  438.         end
  439.     end
  440.  
  441.     ulx.fancyLogAdmin( calling_ply, "#A silently slayed #T", affected_plys )
  442. end
  443. local sslay = ulx.command( CATEGORY_NAME, "ulx sslay", ulx.sslay, "!sslay" )
  444. sslay:addParam{ type=ULib.cmds.PlayersArg }
  445. sslay:defaultAccess( ULib.ACCESS_ADMIN )
  446. sslay:help( "Silently slays target(s)." )
  447.  
  448. ------------------------------ Ignite ------------------------------
  449. function ulx.ignite( calling_ply, target_plys, seconds, should_extinguish )
  450.     local affected_plys = {}
  451.  
  452.     for i=1, #target_plys do
  453.         local v = target_plys[ i ]
  454.  
  455.         if not should_extinguish then
  456.             v:Ignite( seconds )
  457.             v.ulx_ignited_until = CurTime() + seconds
  458.             table.insert( affected_plys, v )
  459.         elseif v:IsOnFire() then
  460.             v:Extinguish()
  461.             v.ulx_ignited_until = nil
  462.             table.insert( affected_plys, v )
  463.         end
  464.     end
  465.  
  466.     if not should_extinguish then
  467.         ulx.fancyLogAdmin( calling_ply, "#A ignited #T for #i seconds", affected_plys, seconds )
  468.     else
  469.         ulx.fancyLogAdmin( calling_ply, "#A extinguished #T", affected_plys )
  470.     end
  471. end
  472. local ignite = ulx.command( CATEGORY_NAME, "ulx ignite", ulx.ignite, "!ignite" )
  473. ignite:addParam{ type=ULib.cmds.PlayersArg }
  474. ignite:addParam{ type=ULib.cmds.NumArg, min=1, max=300, default=300, hint="seconds", ULib.cmds.optional, ULib.cmds.round }
  475. ignite:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  476. ignite:defaultAccess( ULib.ACCESS_ADMIN )
  477. ignite:help( "Ignites target(s)." )
  478. ignite:setOpposite( "ulx unignite", {_, _, _, true}, "!unignite" )
  479.  
  480. local function checkFireDeath( ply )
  481.     if ply.ulx_ignited_until and ply.ulx_ignited_until >= CurTime() and ply:IsOnFire() then
  482.         ply:Extinguish()
  483.         ply.ulx_ignited_until = nil
  484.     end
  485. end
  486. hook.Add( "PlayerDeath", "ULXCheckFireDeath", checkFireDeath, HOOK_MONITOR_HIGH )
  487.  
  488. ------------------------------ Unigniteall ------------------------------
  489. function ulx.unigniteall( calling_ply )
  490.     local flame_ents = ents.FindByClass( 'entityflame' )
  491.     for _,v in ipairs( flame_ents ) do
  492.         if v:IsValid() then
  493.             v:Remove()
  494.         end
  495.     end
  496.  
  497.     local plys = player.GetAll()
  498.     for _, v in ipairs( plys ) do
  499.         if v:IsOnFire() then
  500.             v:Extinguish()
  501.             v.ulx_ignited_until = nil
  502.         end
  503.     end
  504.  
  505.     ulx.fancyLogAdmin( calling_ply, "#A extinguished everything" )
  506. end
  507. local unigniteall = ulx.command( CATEGORY_NAME, "ulx unigniteall", ulx.unigniteall, "!unigniteall" )
  508. unigniteall:defaultAccess( ULib.ACCESS_ADMIN )
  509. unigniteall:help( "Extinguishes all players and all entities." )
  510.  
  511. ------------------------------ Playsound ------------------------------
  512. function ulx.playsound( calling_ply, sound )
  513.     if not ULib.fileExists( "sound/" .. sound ) then
  514.         ULib.tsayError( calling_ply, "That sound doesn't exist on the server!", true )
  515.         return
  516.     end
  517.  
  518.     umsg.Start( "ulib_sound" )
  519.         umsg.String( Sound( sound ) )
  520.     umsg.End()
  521.  
  522.     ulx.fancyLogAdmin( calling_ply, "#A played sound #s", sound )
  523. end
  524. local playsound = ulx.command( CATEGORY_NAME, "ulx playsound", ulx.playsound )
  525. playsound:addParam{ type=ULib.cmds.StringArg, hint="sound", autocomplete_fn=ulx.soundComplete }
  526. playsound:defaultAccess( ULib.ACCESS_ADMIN )
  527. playsound:help( "Plays a sound (relative to sound dir)." )
  528.  
  529. ------------------------------ Freeze ------------------------------
  530. function ulx.freeze( calling_ply, target_plys, should_unfreeze )
  531.     local affected_plys = {}
  532.     for i=1, #target_plys do
  533.         if not should_unfreeze and ulx.getExclusive( target_plys[ i ], calling_ply ) then
  534.             ULib.tsayError( calling_ply, ulx.getExclusive( target_plys[ i ], calling_ply ), true )
  535.         else
  536.             local v = target_plys[ i ]
  537.             if v:InVehicle() then
  538.                 v:ExitVehicle()
  539.             end
  540.  
  541.             if not should_unfreeze then
  542.                 v:Lock()
  543.                 v.frozen = true
  544.                 ulx.setExclusive( v, "frozen" )
  545.             else
  546.                 v:UnLock()
  547.                 v.frozen = nil
  548.                 ulx.clearExclusive( v )
  549.             end
  550.  
  551.             v:DisallowSpawning( not should_unfreeze )
  552.             ulx.setNoDie( v, not should_unfreeze )
  553.             table.insert( affected_plys, v )
  554.  
  555.             if v.whipped then
  556.                 v.whipcount = v.whipamt -- Will make it remove
  557.             end
  558.         end
  559.     end
  560.  
  561.     if not should_unfreeze then
  562.         ulx.fancyLogAdmin( calling_ply, "#A froze #T", affected_plys )
  563.     else
  564.         ulx.fancyLogAdmin( calling_ply, "#A unfroze #T", affected_plys )
  565.     end
  566. end
  567. local freeze = ulx.command( CATEGORY_NAME, "ulx freeze", ulx.freeze, "!freeze" )
  568. freeze:addParam{ type=ULib.cmds.PlayersArg }
  569. freeze:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  570. freeze:defaultAccess( ULib.ACCESS_ADMIN )
  571. freeze:help( "Freezes target(s)." )
  572. freeze:setOpposite( "ulx unfreeze", {_, _, true}, "!unfreeze" )
  573.  
  574. ------------------------------ God ------------------------------
  575. function ulx.god( calling_ply, target_plys, should_revoke )
  576.     if not target_plys[ 1 ]:IsValid() then
  577.         if not should_revoke then
  578.             Msg( "You are the console, you are already god.\n" )
  579.         else
  580.             Msg( "Your position of god is irrevocable; if you don't like it, leave the matrix.\n" )
  581.         end
  582.         return
  583.     end
  584.  
  585.     local affected_plys = {}
  586.     for i=1, #target_plys do
  587.         local v = target_plys[ i ]
  588.  
  589.         if ulx.getExclusive( v, calling_ply ) then
  590.             ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  591.         else
  592.             if not should_revoke then
  593.                 v:GodEnable()
  594.                 v.ULXHasGod = true
  595.             else
  596.                 v:GodDisable()
  597.                 v.ULXHasGod = nil
  598.             end
  599.             table.insert( affected_plys, v )
  600.         end
  601.     end
  602.  
  603.     if not should_revoke then
  604.         ulx.fancyLogAdmin( calling_ply, "#A granted god mode upon #T", affected_plys )
  605.     else
  606.         ulx.fancyLogAdmin( calling_ply, "#A revoked god mode from #T", affected_plys )
  607.     end
  608. end
  609. local god = ulx.command( CATEGORY_NAME, "ulx god", ulx.god, "!god" )
  610. god:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
  611. god:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  612. god:defaultAccess( ULib.ACCESS_ADMIN )
  613. god:help( "Grants god mode to target(s)." )
  614. god:setOpposite( "ulx ungod", {_, _, true}, "!ungod" )
  615.  
  616. ------------------------------ Hp ------------------------------
  617. function ulx.hp( calling_ply, target_plys, amount )
  618.     for i=1, #target_plys do
  619.         target_plys[ i ]:SetHealth( amount )
  620.     end
  621.     ulx.fancyLogAdmin( calling_ply, "#A set the hp for #T to #i", target_plys, amount )
  622. end
  623. local hp = ulx.command( CATEGORY_NAME, "ulx hp", ulx.hp, "!hp" )
  624. hp:addParam{ type=ULib.cmds.PlayersArg }
  625. hp:addParam{ type=ULib.cmds.NumArg, min=1, max=2^32/2-1, hint="hp", ULib.cmds.round }
  626. hp:defaultAccess( ULib.ACCESS_ADMIN )
  627. hp:help( "Sets the hp for target(s)." )
  628.  
  629. ------------------------------ Armor ------------------------------
  630. function ulx.armor( calling_ply, target_plys, amount )
  631.     for i=1, #target_plys do
  632.         target_plys[ i ]:SetArmor( amount )
  633.     end
  634.     ulx.fancyLogAdmin( calling_ply, "#A set the armor for #T to #i", target_plys, amount )
  635. end
  636. local armor = ulx.command( CATEGORY_NAME, "ulx armor", ulx.armor, "!armor" )
  637. armor:addParam{ type=ULib.cmds.PlayersArg }
  638. armor:addParam{ type=ULib.cmds.NumArg, min=0, max=255, hint="armor", ULib.cmds.round }
  639. armor:defaultAccess( ULib.ACCESS_ADMIN )
  640. armor:help( "Sets the armor for target(s)." )
  641.  
  642. ------------------------------ Cloak ------------------------------
  643. function ulx.cloak( calling_ply, target_plys, amount, should_uncloak )
  644.     if not target_plys[ 1 ]:IsValid() then
  645.         Msg( "You are always invisible.\n" )
  646.         return
  647.     end
  648.  
  649.     amount = 255 - amount
  650.  
  651.     for i=1, #target_plys do
  652.         ULib.invisible( target_plys[ i ], not should_uncloak, amount )
  653.     end
  654.  
  655.     if not should_uncloak then
  656.         ulx.fancyLogAdmin( calling_ply, "#A cloaked #T by amount #i", target_plys, amount )
  657.     else
  658.         ulx.fancyLogAdmin( calling_ply, "#A uncloaked #T", target_plys )
  659.     end
  660. end
  661. local cloak = ulx.command( CATEGORY_NAME, "ulx cloak", ulx.cloak, "!cloak" )
  662. cloak:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
  663. cloak:addParam{ type=ULib.cmds.NumArg, min=0, max=255, default=255, hint="amount", ULib.cmds.round, ULib.cmds.optional }
  664. cloak:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  665. cloak:defaultAccess( ULib.ACCESS_ADMIN )
  666. cloak:help( "Cloaks target(s)." )
  667. cloak:setOpposite( "ulx uncloak", {_, _, _, true}, "!uncloak" )
  668.  
  669. ------------------------------ Blind ------------------------------
  670. function ulx.blind( calling_ply, target_plys, amount, should_unblind )
  671.     for i=1, #target_plys do
  672.         local v = target_plys[ i ]
  673.         umsg.Start( "ulx_blind", v )
  674.             umsg.Bool( not should_unblind )
  675.             umsg.Short( amount )
  676.         umsg.End()
  677.  
  678.         if should_unblind then
  679.             if v.HadCamera then
  680.                 v:Give( "gmod_camera" )
  681.             end
  682.             v.HadCamera = nil
  683.         else
  684.             if v.HadCamera == nil then -- In case blind is run twice
  685.                 v.HadCamera = v:HasWeapon( "gmod_camera" )
  686.             end
  687.             v:StripWeapon( "gmod_camera" )
  688.         end
  689.     end
  690.  
  691.     if not should_unblind then
  692.         ulx.fancyLogAdmin( calling_ply, "#A blinded #T by amount #i", target_plys, amount )
  693.     else
  694.         ulx.fancyLogAdmin( calling_ply, "#A unblinded #T", target_plys )
  695.     end
  696. end
  697. local blind = ulx.command( CATEGORY_NAME, "ulx blind", ulx.blind, "!blind" )
  698. blind:addParam{ type=ULib.cmds.PlayersArg }
  699. blind:addParam{ type=ULib.cmds.NumArg, min=0, max=255, default=255, hint="amount", ULib.cmds.round, ULib.cmds.optional }
  700. blind:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  701. blind:defaultAccess( ULib.ACCESS_ADMIN )
  702. blind:help( "Blinds target(s)." )
  703. blind:setOpposite( "ulx unblind", {_, _, _, true}, "!unblind" )
  704.  
  705. ------------------------------ Jail ------------------------------
  706. local doJail
  707. local jailableArea
  708. function ulx.jail( calling_ply, target_plys, seconds, should_unjail )
  709.     local affected_plys = {}
  710.     for i=1, #target_plys do
  711.         local v = target_plys[ i ]
  712.  
  713.         if not should_unjail then
  714.             if ulx.getExclusive( v, calling_ply ) then
  715.                 ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  716.             elseif not jailableArea( v:GetPos() ) then
  717.                 ULib.tsayError( calling_ply, v:Nick() .. " is not in an area where a jail can be placed!", true )
  718.             else
  719.                 doJail( v, seconds )
  720.  
  721.                 table.insert( affected_plys, v )
  722.             end
  723.         elseif v.jail then
  724.             v.jail.unjail()
  725.             v.jail = nil
  726.             table.insert( affected_plys, v )
  727.         end
  728.     end
  729.  
  730.     if not should_unjail then
  731.         local str = "#A jailed #T"
  732.         if seconds > 0 then
  733.             str = str .. " for #i seconds"
  734.         end
  735.         ulx.fancyLogAdmin( calling_ply, str, affected_plys, seconds )
  736.     else
  737.         ulx.fancyLogAdmin( calling_ply, "#A unjailed #T", affected_plys )
  738.     end
  739. end
  740. local jail = ulx.command( CATEGORY_NAME, "ulx jail", ulx.jail, "!jail" )
  741. jail:addParam{ type=ULib.cmds.PlayersArg }
  742. jail:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="seconds, 0 is forever", ULib.cmds.round, ULib.cmds.optional }
  743. jail:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  744. jail:defaultAccess( ULib.ACCESS_ADMIN )
  745. jail:help( "Jails target(s)." )
  746. jail:setOpposite( "ulx unjail", {_, _, _, true}, "!unjail" )
  747.  
  748. ------------------------------ Jail TP ------------------------------
  749. function ulx.jailtp( calling_ply, target_ply, seconds )
  750.     local t = {}
  751.     t.start = calling_ply:GetPos() + Vector( 0, 0, 32 ) -- Move them up a bit so they can travel across the ground
  752.     t.endpos = calling_ply:GetPos() + calling_ply:EyeAngles():Forward() * 16384
  753.     t.filter = target_ply
  754.     if target_ply ~= calling_ply then
  755.         t.filter = { target_ply, calling_ply }
  756.     end
  757.     local tr = util.TraceEntity( t, target_ply )
  758.  
  759.     local pos = tr.HitPos
  760.  
  761.     if ulx.getExclusive( target_ply, calling_ply ) then
  762.         ULib.tsayError( calling_ply, ulx.getExclusive( target_ply, calling_ply ), true )
  763.         return
  764.     elseif not target_ply:Alive() then
  765.         ULib.tsayError( calling_ply, target_ply:Nick() .. " is dead!", true )
  766.         return
  767.     elseif not jailableArea( pos ) then
  768.         ULib.tsayError( calling_ply, "That is not an area where a jail can be placed!", true )
  769.         return
  770.     else
  771.         target_ply.ulx_prevpos = target_ply:GetPos()
  772.         target_ply.ulx_prevang = target_ply:EyeAngles()
  773.  
  774.         if target_ply:InVehicle() then
  775.             target_ply:ExitVehicle()
  776.         end
  777.  
  778.         target_ply:SetPos( pos )
  779.         target_ply:SetLocalVelocity( Vector( 0, 0, 0 ) ) -- Stop!
  780.  
  781.         doJail( target_ply, seconds )
  782.     end
  783.  
  784.     local str = "#A teleported and jailed #T"
  785.     if seconds > 0 then
  786.         str = str .. " for #i seconds"
  787.     end
  788.     ulx.fancyLogAdmin( calling_ply, str, target_ply, seconds )
  789. end
  790. local jailtp = ulx.command( CATEGORY_NAME, "ulx jailtp", ulx.jailtp, "!jailtp" )
  791. jailtp:addParam{ type=ULib.cmds.PlayerArg }
  792. jailtp:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="seconds, 0 is forever", ULib.cmds.round, ULib.cmds.optional }
  793. jailtp:defaultAccess( ULib.ACCESS_ADMIN )
  794. jailtp:help( "Teleports, then jails target(s)." )
  795.  
  796. local function jailCheck()
  797.     local remove_timer = true
  798.     local players = player.GetAll()
  799.     for i=1, #players do
  800.         local ply = players[ i ]
  801.         if ply.jail then
  802.             remove_timer = false
  803.         end
  804.         if ply.jail and (ply.jail.pos-ply:GetPos()):LengthSqr() >= 6500 then
  805.             ply:SetPos( ply.jail.pos )
  806.             if ply.jail.jail_until then
  807.                 doJail( ply, ply.jail.jail_until - CurTime() )
  808.             else
  809.                 doJail( ply, 0 )
  810.             end
  811.         end
  812.     end
  813.  
  814.     if remove_timer then
  815.         timer.Remove( "ULXJail" )
  816.     end
  817. end
  818.  
  819. jailableArea = function( pos )
  820.     entList = ents.FindInBox( pos - Vector( 35, 35, 5 ), pos + Vector( 35, 35, 110 ) )
  821.     for i=1, #entList do
  822.         if entList[ i ]:GetClass() == "trigger_remove" then
  823.             return false
  824.         end
  825.     end
  826.  
  827.     return true
  828. end
  829.  
  830. local mdl1 = Model( "models/props_building_details/Storefront_Template001a_Bars.mdl" )
  831. local jail = {
  832.     { pos = Vector( 0, 0, -5 ), ang = Angle( 90, 0, 0 ), mdl=mdl1 },
  833.     { pos = Vector( 0, 0, 97 ), ang = Angle( 90, 0, 0 ), mdl=mdl1 },
  834.     { pos = Vector( 21, 31, 46 ), ang = Angle( 0, 90, 0 ), mdl=mdl1 },
  835.     { pos = Vector( 21, -31, 46 ), ang = Angle( 0, 90, 0 ), mdl=mdl1 },
  836.     { pos = Vector( -21, 31, 46 ), ang = Angle( 0, 90, 0 ), mdl=mdl1 },
  837.     { pos = Vector( -21, -31, 46), ang = Angle( 0, 90, 0 ), mdl=mdl1 },
  838.     { pos = Vector( -52, 0, 46 ), ang = Angle( 0, 0, 0 ), mdl=mdl1 },
  839.     { pos = Vector( 52, 0, 46 ), ang = Angle( 0, 0, 0 ), mdl=mdl1 },
  840. }
  841. doJail = function( v, seconds )
  842.     if v.jail then -- They're already jailed
  843.         v.jail.unjail()
  844.     end
  845.  
  846.     if v:InVehicle() then
  847.         local vehicle = v:GetParent()
  848.         v:ExitVehicle()
  849.         vehicle:Remove()
  850.     end
  851.  
  852.     -- Force other players to let go of this player
  853.     if v.physgunned_by then
  854.         for ply, v in pairs( v.physgunned_by ) do
  855.             if ply:IsValid() and ply:GetActiveWeapon():IsValid() and ply:GetActiveWeapon():GetClass() == "weapon_physgun" then
  856.                 ply:ConCommand( "-attack" )
  857.             end
  858.         end
  859.     end
  860.  
  861.     if v:GetMoveType() == MOVETYPE_NOCLIP then -- Take them out of noclip
  862.         v:SetMoveType( MOVETYPE_WALK )
  863.     end
  864.  
  865.     local pos = v:GetPos()
  866.  
  867.     local walls = {}
  868.     for _, info in ipairs( jail ) do
  869.         local ent = ents.Create( "prop_physics" )
  870.         ent:SetModel( info.mdl )
  871.         ent:SetPos( pos + info.pos )
  872.         ent:SetAngles( info.ang )
  873.         ent:Spawn()
  874.         ent:GetPhysicsObject():EnableMotion( false )
  875.         ent:SetMoveType( MOVETYPE_NONE )
  876.         ent.jailWall = true
  877.         table.insert( walls, ent )
  878.     end
  879.  
  880.     local key = {}
  881.     local function unjail()
  882.         if not v:IsValid() or not v.jail or v.jail.key ~= key then -- Nope
  883.             return
  884.         end
  885.  
  886.         for _, ent in ipairs( walls ) do
  887.             if ent:IsValid() then
  888.                 ent:DisallowDeleting( false )
  889.                 ent:Remove()
  890.             end
  891.         end
  892.         if not v:IsValid() then return end -- Make sure they're still connected
  893.  
  894.         v:DisallowNoclip( false )
  895.         v:DisallowMoving( false )
  896.         v:DisallowSpawning( false )
  897.         v:DisallowVehicles( false )
  898.  
  899.         ulx.clearExclusive( v )
  900.         ulx.setNoDie( v, false )
  901.  
  902.         v.jail = nil
  903.     end
  904.     if seconds > 0 then
  905.         timer.Simple( seconds, unjail )
  906.     end
  907.  
  908.     local function newWall( old, new )
  909.         table.insert( walls, new )
  910.     end
  911.  
  912.     for _, ent in ipairs( walls ) do
  913.         ent:DisallowDeleting( true, newWall )
  914.         ent:DisallowMoving( true )
  915.     end
  916.     v:DisallowNoclip( true )
  917.     v:DisallowMoving( true )
  918.     v:DisallowSpawning( true )
  919.     v:DisallowVehicles( true )
  920.     v.jail = { pos=pos, unjail=unjail, key=key }
  921.     if seconds > 0 then
  922.         v.jail.jail_until = CurTime() + seconds
  923.     end
  924.     ulx.setExclusive( v, "in jail" )
  925.     ulx.setNoDie( v, true )
  926.  
  927.     timer.Create( "ULXJail", 1, 0, jailCheck )
  928. end
  929.  
  930. local function jailDisconnectedCheck( ply )
  931.     if ply.jail then
  932.         ply.jail.unjail()
  933.     end
  934. end
  935. hook.Add( "PlayerDisconnected", "ULXJailDisconnectedCheck", jailDisconnectedCheck, HOOK_MONITOR_HIGH )
  936.  
  937. local function playerPickup( ply, ent )
  938.     if CLIENT then return end
  939.     if ent:IsPlayer() then
  940.         ent.physgunned_by = ent.physgunned_by or {}
  941.         ent.physgunned_by[ ply ] = true
  942.     end
  943. end
  944. hook.Add( "PhysgunPickup", "ulxPlayerPickupJailCheck", playerPickup, HOOK_MONITOR_HIGH )
  945.  
  946. local function playerDrop( ply, ent )
  947.     if CLIENT then return end
  948.     if ent:IsPlayer() and ent.physgunned_by then
  949.         ent.physgunned_by[ ply ] = nil
  950.     end
  951. end
  952. hook.Add( "PhysgunDrop", "ulxPlayerDropJailCheck", playerDrop )
  953.  
  954. ------------------------------ Ragdoll ------------------------------
  955. local function ragdollPlayer( v )
  956.     if v:InVehicle() then
  957.         local vehicle = v:GetParent()
  958.         v:ExitVehicle()
  959.     end
  960.  
  961.     ULib.getSpawnInfo( v ) -- Collect information so we can respawn them in the same state.
  962.  
  963.     local ragdoll = ents.Create( "prop_ragdoll" )
  964.     ragdoll.ragdolledPly = v
  965.  
  966.     ragdoll:SetPos( v:GetPos() )
  967.     local velocity = v:GetVelocity()
  968.     ragdoll:SetAngles( v:GetAngles() )
  969.     ragdoll:SetModel( v:GetModel() )
  970.     ragdoll:Spawn()
  971.     ragdoll:Activate()
  972.     v:SetParent( ragdoll ) -- So their player ent will match up (position-wise) with where their ragdoll is.
  973.     -- Set velocity for each piece of the ragdoll
  974.     local j = 1
  975.     while true do -- Break inside
  976.         local phys_obj = ragdoll:GetPhysicsObjectNum( j )
  977.         if phys_obj then
  978.             phys_obj:SetVelocity( velocity )
  979.             j = j + 1
  980.         else
  981.             break
  982.         end
  983.     end
  984.  
  985.     v:Spectate( OBS_MODE_CHASE )
  986.     v:SpectateEntity( ragdoll )
  987.     v:StripWeapons() -- Otherwise they can still use the weapons.
  988.  
  989.     ragdoll:DisallowDeleting( true, function( old, new )
  990.         if v:IsValid() then v.ragdoll = new end
  991.     end )
  992.     v:DisallowSpawning( true )
  993.  
  994.     v.ragdoll = ragdoll
  995.     ulx.setExclusive( v, "ragdolled" )
  996. end
  997.  
  998. local function unragdollPlayer( v )
  999.     v:DisallowSpawning( false )
  1000.     v:SetParent()
  1001.  
  1002.     v:UnSpectate() -- Need this for DarkRP for some reason, works fine without it in sbox
  1003.  
  1004.     local ragdoll = v.ragdoll
  1005.     v.ragdoll = nil -- Gotta do this before spawn or our hook catches it
  1006.  
  1007.     if not ragdoll:IsValid() then -- Something must have removed it, just spawn
  1008.         ULib.spawn( v, true )
  1009.  
  1010.     else
  1011.         local pos = ragdoll:GetPos()
  1012.         pos.z = pos.z + 10 -- So they don't end up in the ground
  1013.  
  1014.         ULib.spawn( v, true )
  1015.         v:SetPos( pos )
  1016.         v:SetVelocity( ragdoll:GetVelocity() )
  1017.         local yaw = ragdoll:GetAngles().yaw
  1018.         v:SetAngles( Angle( 0, yaw, 0 ) )
  1019.         ragdoll:DisallowDeleting( false )
  1020.         ragdoll:Remove()
  1021.     end
  1022.  
  1023.     ulx.clearExclusive( v )
  1024. end
  1025.  
  1026. function ulx.ragdoll( calling_ply, target_plys, should_unragdoll )
  1027.     local affected_plys = {}
  1028.     for i=1, #target_plys do
  1029.         local v = target_plys[ i ]
  1030.  
  1031.         if not should_unragdoll then
  1032.             if ulx.getExclusive( v, calling_ply ) then
  1033.                 ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  1034.             elseif not v:Alive() then
  1035.                 ULib.tsayError( calling_ply, v:Nick() .. " is dead and cannot be ragdolled!", true )
  1036.             else
  1037.                 ragdollPlayer( v )
  1038.                 table.insert( affected_plys, v )
  1039.             end
  1040.         elseif v.ragdoll then -- Only if they're ragdolled...
  1041.             unragdollPlayer( v )
  1042.             table.insert( affected_plys, v )
  1043.         end
  1044.     end
  1045.  
  1046.     if not should_unragdoll then
  1047.         ulx.fancyLogAdmin( calling_ply, "#A ragdolled #T", affected_plys )
  1048.     else
  1049.         ulx.fancyLogAdmin( calling_ply, "#A unragdolled #T", affected_plys )
  1050.     end
  1051. end
  1052. local ragdoll = ulx.command( CATEGORY_NAME, "ulx ragdoll", ulx.ragdoll, "!ragdoll" )
  1053. ragdoll:addParam{ type=ULib.cmds.PlayersArg }
  1054. ragdoll:addParam{ type=ULib.cmds.BoolArg, invisible=true }
  1055. ragdoll:defaultAccess( ULib.ACCESS_ADMIN )
  1056. ragdoll:help( "ragdolls target(s)." )
  1057. ragdoll:setOpposite( "ulx unragdoll", {_, _, true}, "!unragdoll" )
  1058.  
  1059. local function ragdollSpawnCheck( ply )
  1060.     if ply.ragdoll then
  1061.         timer.Simple( 0.01, function() -- Doesn't like us using it instantly
  1062.             if not ply:IsValid() then return end -- Make sure they're still here
  1063.             ply:Spectate( OBS_MODE_CHASE )
  1064.             ply:SpectateEntity( ply.ragdoll )
  1065.             ply:StripWeapons() -- Otherwise they can still use the weapons.
  1066.         end )
  1067.     end
  1068. end
  1069. hook.Add( "PlayerSpawn", "ULXRagdollSpawnCheck", ragdollSpawnCheck )
  1070.  
  1071. local function ragdollDisconnectedCheck( ply )
  1072.     if ply.ragdoll then
  1073.         ply.ragdoll:DisallowDeleting( false )
  1074.         ply.ragdoll:Remove()
  1075.     end
  1076. end
  1077. hook.Add( "PlayerDisconnected", "ULXRagdollDisconnectedCheck", ragdollDisconnectedCheck, HOOK_MONITOR_HIGH )
  1078.  
  1079. local function removeRagdollOnCleanup()
  1080.     local players = player.GetAll()
  1081.     for i=1, #players do
  1082.         local ply = players[i]
  1083.         if ply.ragdoll then
  1084.             ply.ragdollAfterCleanup = true
  1085.             unragdollPlayer( ply )
  1086.         end
  1087.     end
  1088. end
  1089. hook.Add("PreCleanupMap","ULXRagdollBeforeCleanup", removeRagdollOnCleanup )
  1090.  
  1091. local function createRagdollAfterCleanup()
  1092.     local players = player.GetAll()
  1093.     for i=1, #players do
  1094.         local ply = players[i]
  1095.         if ply.ragdollAfterCleanup then
  1096.             ply.ragdollAfterCleanup = nil
  1097.             timer.Simple( 0.1, function() -- Doesn't like us re-creating the ragdoll immediately
  1098.                 ragdollPlayer( ply )
  1099.             end)
  1100.         end
  1101.     end
  1102. end
  1103. hook.Add("PostCleanupMap","ULXRagdollAfterCleanup", createRagdollAfterCleanup )
  1104.  
  1105. ------------------------------ Maul ------------------------------
  1106. local zombieDeath -- We need these registered up here because functions reference each other.
  1107. local checkMaulDeath
  1108.  
  1109. local function newZombie( pos, ang, ply, b )
  1110.         local ent = ents.Create( "npc_fastzombie" )
  1111.         ent:SetPos( pos )
  1112.         ent:SetAngles( ang )
  1113.         ent:Spawn()
  1114.         ent:Activate()
  1115.         ent:AddRelationship("player D_NU 98") -- Don't attack other players
  1116.         ent:AddEntityRelationship( ply, D_HT, 99 ) -- Hate target
  1117.  
  1118.         ent:DisallowDeleting( true, _, true )
  1119.         ent:DisallowMoving( true )
  1120.  
  1121.         if not b then
  1122.             ent:CallOnRemove( "NoDie", zombieDeath, ply )
  1123.         end
  1124.  
  1125.         return ent
  1126. end
  1127.  
  1128. -- Utility function
  1129. zombieDeath = function( ent, ply )
  1130.     if ply.maul_npcs then -- Recreate!
  1131.         local pos = ent:GetPos()
  1132.         local ang = ent:GetAngles()
  1133.         ULib.queueFunctionCall( function() -- Create it next frame because 1. Old NPC won't be in way and 2. We won't overflow the server while shutting down with someone being mauled
  1134.             if not ply:IsValid() then return end -- Player left
  1135.  
  1136.             local ent2 = newZombie( pos, ang, ply )
  1137.             table.insert( ply.maul_npcs, ent2 ) -- Don't worry about removing the old one, doesn't matter.
  1138.  
  1139.             -- Make sure we didn't make a headcrab!
  1140.             local ents = ents.FindByClass( "npc_headcrab_fast" )
  1141.             for _, ent in ipairs( ents ) do
  1142.                 dist = ent:GetPos():Distance( pos )
  1143.                 if dist < 128 then -- Assume it's from the zombies
  1144.                     ent:Remove()
  1145.                 end
  1146.             end
  1147.         end )
  1148.     end
  1149. end
  1150.  
  1151. -- Another utility for maul
  1152. local function maulMoreDamage()
  1153.     local players = player.GetAll()
  1154.     for _, ply in ipairs( players ) do
  1155.         if ply.maul_npcs and ply:Alive() then
  1156.             if CurTime() > ply.maulStart + 10 then
  1157.                 local damage = math.ceil( ply.maulStartHP / 10 ) -- Damage per second
  1158.                 damage = damage * FrameTime() -- Damage this frame
  1159.                 damage = math.ceil( damage )
  1160.                 local newhp = ply:Health() - damage
  1161.                 if newhp < 1 then newhp = 1 end
  1162.                 ply:SetHealth( newhp ) -- We don't use takedamage because the player slides across the ground.
  1163.                 if CurTime() > ply.maulStart + 20 then
  1164.                     ply:Kill() -- Worst case senario.
  1165.                     checkMaulDeath( ply ) -- Just in case the death hook is broken
  1166.                 end
  1167.             end
  1168.             ply.maul_lasthp = ply:Health()
  1169.         end
  1170.     end
  1171. end
  1172.  
  1173. function ulx.maul( calling_ply, target_plys )
  1174.     local affected_plys = {}
  1175.     for i=1, #target_plys do
  1176.         local v = target_plys[ i ]
  1177.  
  1178.         if ulx.getExclusive( v, calling_ply ) then
  1179.             ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  1180.  
  1181.         elseif not v:Alive() then
  1182.             ULib.tsayError( calling_ply, v:Nick() .. " is dead!", true )
  1183.  
  1184.         else
  1185.             local pos = {}
  1186.             local testent = newZombie( Vector( 0, 0, 0 ), Angle( 0, 0, 0 ), v, true ) -- Test ent for traces
  1187.  
  1188.             local yawForward = v:EyeAngles().yaw
  1189.             local directions = { -- Directions to try
  1190.                 math.NormalizeAngle( yawForward - 180 ), -- Behind first
  1191.                 math.NormalizeAngle( yawForward + 90 ), -- Right
  1192.                 math.NormalizeAngle( yawForward - 90 ), -- Left
  1193.                 yawForward,
  1194.             }
  1195.  
  1196.             local t = {}
  1197.             t.start = v:GetPos() + Vector( 0, 0, 32 ) -- Move them up a bit so they can travel across the ground
  1198.             t.filter = { v, testent }
  1199.  
  1200.             for i=1, #directions do -- Check all directions
  1201.                 t.endpos = v:GetPos() + Angle( 0, directions[ i ], 0 ):Forward() * 47 -- (33 is player width, this is sqrt( 33^2 * 2 ))
  1202.                 local tr = util.TraceEntity( t, testent )
  1203.  
  1204.                 if not tr.Hit then
  1205.                     table.insert( pos, v:GetPos() + Angle( 0, directions[ i ], 0 ):Forward() * 47 )
  1206.                 end
  1207.             end
  1208.  
  1209.             testent:DisallowDeleting( false )
  1210.             testent:Remove() -- Don't forget to remove our friend now!
  1211.  
  1212.             if #pos > 0 then
  1213.                 v.maul_npcs = {}
  1214.                 for _, newpos in ipairs( pos ) do
  1215.                     local newang = (v:GetPos() - newpos):Angle()
  1216.  
  1217.                     local ent = newZombie( newpos, newang, v )
  1218.                     table.insert( v.maul_npcs, ent )
  1219.                 end
  1220.  
  1221.                 v:SetMoveType( MOVETYPE_WALK )
  1222.                 v:DisallowNoclip( true )
  1223.                 v:DisallowSpawning( true )
  1224.                 v:DisallowVehicles( true )
  1225.                 v:GodDisable()
  1226.                 v:SetArmor( 0 ) -- Armor takes waaaay too long for them to take down
  1227.                 v.maulOrigWalk = v:GetWalkSpeed()
  1228.                 v.maulOrigSprint = v:GetRunSpeed()
  1229.                 v:SetWalkSpeed(1)
  1230.                 v:SetRunSpeed(1)
  1231.  
  1232.                 v.maulStart = CurTime()
  1233.                 v.maulStartHP = v:Health()
  1234.                 hook.Add( "Think", "MaulMoreDamageThink", maulMoreDamage )
  1235.  
  1236.                 ulx.setExclusive( v, "being mauled" )
  1237.  
  1238.                 table.insert( affected_plys, v )
  1239.             else
  1240.                 ULib.tsayError( calling_ply, "Can't find a place to put the npcs for " .. v:Nick(), true )
  1241.             end
  1242.         end
  1243.     end
  1244.  
  1245.     ulx.fancyLogAdmin( calling_ply, "#A mauled #T", affected_plys )
  1246. end
  1247. local maul = ulx.command( CATEGORY_NAME, "ulx maul", ulx.maul, "!maul" )
  1248. maul:addParam{ type=ULib.cmds.PlayersArg }
  1249. maul:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1250. maul:help( "Maul target(s)." )
  1251.  
  1252. checkMaulDeath = function( ply, weapon, killer )
  1253.     if ply.maul_npcs then
  1254.         if killer == ply and CurTime() < ply.maulStart + 20 then -- Suicide
  1255.             ply:AddFrags( 1 ) -- Won't show on scoreboard
  1256.             local pos = ply:GetPos()
  1257.             local ang = ply:EyeAngles()
  1258.             ULib.queueFunctionCall( function()
  1259.                 if not ply:IsValid() then return end -- They left
  1260.  
  1261.                 ply:Spawn()
  1262.                 ply:SetPos( pos )
  1263.                 ply:SetEyeAngles( ang )
  1264.                 ply:SetArmor( 0 )
  1265.                 ply:SetHealth( ply.maul_lasthp )
  1266.                 timer.Simple( 0.1, function()
  1267.                     if not ply:IsValid() then return end -- They left
  1268.                     ply:SetCollisionGroup( COLLISION_GROUP_WORLD )
  1269.                     ply:SetWalkSpeed(1)
  1270.                     ply:SetRunSpeed(1)
  1271.                 end )
  1272.             end )
  1273.             return true -- Don't register their death on HUD
  1274.         end
  1275.  
  1276.         local npcs = ply.maul_npcs
  1277.         ply.maul_npcs = nil -- We have to do it this way to signal that we're done mauling
  1278.         for _, ent in ipairs( npcs ) do
  1279.             if ent:IsValid() then
  1280.                 ent:DisallowDeleting( false )
  1281.                 ent:Remove()
  1282.             end
  1283.         end
  1284.         ulx.clearExclusive( ply )
  1285.         ply.maulStart = nil
  1286.         ply.maul_lasthp = nil
  1287.  
  1288.         ply:DisallowNoclip( false )
  1289.         ply:DisallowSpawning( false )
  1290.         ply:DisallowVehicles( false )
  1291.         ply:SetWalkSpeed(ply.maulOrigWalk)
  1292.         ply:SetRunSpeed(ply.maulOrigSprint)
  1293.         ply.maulOrigWalk = nil
  1294.         ply.maulOrigSprint = nil
  1295.  
  1296.         ulx.clearExclusive( ply )
  1297.  
  1298.         -- Now let's check if there's still players being mauled
  1299.         local players = player.GetAll()
  1300.         for _, ply in ipairs( players ) do
  1301.             if ply.maul_npcs then
  1302.                 return
  1303.             end
  1304.         end
  1305.  
  1306.         -- No more? Remove hook.
  1307.         hook.Remove( "Think", "MaulMoreDamageThink" )
  1308.     end
  1309. end
  1310. hook.Add( "PlayerDeath", "ULXCheckMaulDeath", checkMaulDeath, HOOK_HIGH ) -- Hook it first because we're changing speed. Want others to override us.
  1311.  
  1312. local function maulDisconnectedCheck( ply )
  1313.     checkMaulDeath( ply ) -- Just run it through the death function
  1314. end
  1315. hook.Add( "PlayerDisconnected", "ULXMaulDisconnectedCheck", maulDisconnectedCheck, HOOK_MONITOR_HIGH )
  1316.  
  1317. ------------------------------ Strip ------------------------------
  1318. function ulx.stripweapons( calling_ply, target_plys )
  1319.     for i=1, #target_plys do
  1320.         target_plys[ i ]:StripWeapons()
  1321.     end
  1322.  
  1323.     ulx.fancyLogAdmin( calling_ply, "#A stripped weapons from #T", target_plys )
  1324. end
  1325. local strip = ulx.command( CATEGORY_NAME, "ulx strip", ulx.stripweapons, "!strip" )
  1326. strip:addParam{ type=ULib.cmds.PlayersArg }
  1327. strip:defaultAccess( ULib.ACCESS_ADMIN )
  1328. strip:help( "Strip weapons from target(s)." )
  1329.  
  1330. local CATEGORY_NAME = "Menus"
  1331.  
  1332. if ULib.fileExists( "lua/ulx/modules/cl/motdmenu.lua" ) or ulx.motdmenu_exists then
  1333.     local function sendMotd( ply, showMotd )
  1334.         if ply.ulxHasMotd then return end -- This player already has the motd data
  1335.         if showMotd == "1" then -- Assume it's a file
  1336.             if not ULib.fileExists( GetConVarString( "ulx_motdfile" ) ) then return end -- Invalid
  1337.             local f = ULib.fileRead( GetConVarString( "ulx_motdfile" ) )
  1338.  
  1339.             ULib.clientRPC( ply, "ulx.rcvMotd", showMotd, f )
  1340.  
  1341.         elseif showMotd == "2" then
  1342.             ULib.clientRPC( ply, "ulx.rcvMotd", showMotd, ulx.motdSettings )
  1343.  
  1344.         else -- Assume URL
  1345.             ULib.clientRPC( ply, "ulx.rcvMotd", showMotd, GetConVarString( "ulx_motdurl" ) )
  1346.         end
  1347.         ply.ulxHasMotd = true
  1348.     end
  1349.  
  1350.     local function showMotd( ply )
  1351.         local showMotd = GetConVarString( "ulx_showMotd" )
  1352.         if showMotd == "0" then return end
  1353.         if not ply:IsValid() then return end -- They left, doh!
  1354.  
  1355.         sendMotd( ply, showMotd )
  1356.         ULib.clientRPC( ply, "ulx.showMotdMenu", ply:SteamID() ) -- Passing it because they may get it before LocalPlayer() is valid
  1357.     end
  1358.     hook.Add( "PlayerInitialSpawn", "showMotd", showMotd )
  1359.  
  1360.     function ulx.motdUpdated()
  1361.         for i=1, #player.GetAll() do
  1362.             player.GetAll()[i].ulxHasMotd = false
  1363.         end
  1364.     end
  1365.  
  1366.     local function conVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  1367.         if string.lower( cl_cvar ) == "ulx_showmotd" or string.lower( cl_cvar ) == "ulx_motdfile" or string.lower( cl_cvar ) == "ulx_motdurl" then
  1368.             ulx.motdUpdated()
  1369.         end
  1370.     end
  1371.     hook.Add( "ULibReplicatedCvarChanged", "ulx.clearMotdCache", conVarUpdated )
  1372.  
  1373.     function ulx.motd( calling_ply )
  1374.         if not calling_ply:IsValid() then
  1375.             Msg( "You can't see the motd from the console.\n" )
  1376.             return
  1377.         end
  1378.  
  1379.         if GetConVarString( "ulx_showMotd" ) == "0" then
  1380.             ULib.tsay( calling_ply, "The MOTD has been disabled on this server." )
  1381.             return
  1382.         end
  1383.  
  1384.         if GetConVarString( "ulx_showMotd" ) == "1" and not ULib.fileExists( GetConVarString( "ulx_motdfile" ) ) then
  1385.             ULib.tsay( calling_ply, "The MOTD file could not be found." )
  1386.             return
  1387.         end
  1388.  
  1389.         showMotd( calling_ply )
  1390.     end
  1391.     local motdmenu = ulx.command( CATEGORY_NAME, "ulx motd", ulx.motd, "!motd" )
  1392.     motdmenu:defaultAccess( ULib.ACCESS_ALL )
  1393.     motdmenu:help( "Show the message of the day." )
  1394.  
  1395.     if SERVER then
  1396.         ulx.convar( "showMotd", "2", " <0/1/2/3> - MOTD mode. 0 is off.", ULib.ACCESS_ADMIN )
  1397.         ulx.convar( "motdfile", "ulx_motd.txt", "MOTD filepath from gmod root to use if ulx showMotd is 1.", ULib.ACCESS_ADMIN )
  1398.         ulx.convar( "motdurl", "ulyssesmod.net", "MOTD URL to use if ulx showMotd is 3.", ULib.ACCESS_ADMIN )
  1399.  
  1400.         function ulx.populateMotdData()
  1401.             if ulx.motdSettings == nil or ulx.motdSettings.info == nil then return end
  1402.  
  1403.             ulx.motdSettings.admins = {}
  1404.  
  1405.             local getAddonInfo = false
  1406.  
  1407.             -- Gather addon/admin information to display
  1408.             for i=1, #ulx.motdSettings.info do
  1409.                 local sectionInfo = ulx.motdSettings.info[i]
  1410.                 if sectionInfo.type == "mods" and not ulx.motdSettings.addons then
  1411.                     getAddonInfo = true
  1412.                 elseif sectionInfo.type == "admins" then
  1413.                     for a=1, #sectionInfo.contents do
  1414.                         ulx.motdSettings.admins[sectionInfo.contents[a]] = true
  1415.                     end
  1416.                 end
  1417.             end
  1418.  
  1419.             if getAddonInfo then
  1420.                 ulx.motdSettings.addons = {}
  1421.                 local addons = engine.GetAddons()
  1422.                 for i=1, #addons do
  1423.                     local addon = addons[i]
  1424.                     if addon.mounted then
  1425.                         table.insert( ulx.motdSettings.addons, { title=addon.title, workshop_id=addon.file:gsub("%D", "") } )
  1426.                     end
  1427.                 end
  1428.  
  1429.                 local _, possibleaddons = file.Find( "addons/*", "GAME" )
  1430.                 for _, addon in ipairs( possibleaddons ) do
  1431.                     if ULib.fileExists( "addons/" .. addon .. "/addon.txt" ) then
  1432.                         local t = ULib.parseKeyValues( ULib.stripComments( ULib.fileRead( "addons/" .. addon .. "/addon.txt" ), "//" ) )
  1433.                         if t and t.AddonInfo then
  1434.                             local name = t.AddonInfo.name or addon
  1435.                             table.insert( ulx.motdSettings.addons, { title=name, author=t.AddonInfo.author_name } )
  1436.                         end
  1437.                     end
  1438.                 end
  1439.  
  1440.                 table.sort( ulx.motdSettings.addons, function(a,b) return string.lower(a.title) < string.lower(b.title) end )
  1441.             end
  1442.  
  1443.             for group, _ in pairs( ulx.motdSettings.admins ) do
  1444.                 ulx.motdSettings.admins[group] = {}
  1445.                 for steamID, data in pairs( ULib.ucl.users ) do
  1446.                     if data.group == group and data.name then
  1447.                         table.insert( ulx.motdSettings.admins[group], data.name )
  1448.                     end
  1449.                 end
  1450.             end
  1451.         end
  1452.         hook.Add( ULib.HOOK_UCLCHANGED, "ulx.updateMotd.adminsChanged", ulx.populateMotdData )
  1453.     end
  1454.  
  1455. end
  1456. -- This module holds any type of remote execution functions (IE, 'dangerous')
  1457. local CATEGORY_NAME = "Rcon"
  1458.  
  1459. function ulx.rcon( calling_ply, command )
  1460.     ULib.consoleCommand( command .. "\n" )
  1461.  
  1462.     ulx.fancyLogAdmin( calling_ply, true, "#A ran rcon command: #s", command )
  1463. end
  1464. local rcon = ulx.command( CATEGORY_NAME, "ulx rcon", ulx.rcon, "!rcon", true, false, true )
  1465. rcon:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
  1466. rcon:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1467. rcon:help( "Execute command on server console." )
  1468.  
  1469. function ulx.luaRun( calling_ply, command )
  1470.     local return_results = false
  1471.     if command:sub( 1, 1 ) == "=" then
  1472.         command = "tmp_var" .. command
  1473.         return_results = true
  1474.     end
  1475.  
  1476.     RunString( command )
  1477.  
  1478.     if return_results then
  1479.         if type( tmp_var ) == "table" then
  1480.             ULib.console( calling_ply, "Result:" )
  1481.             local lines = ULib.explode( "\n", ulx.dumpTable( tmp_var ) )
  1482.             local chunk_size = 50
  1483.             for i=1, #lines, chunk_size do -- Break it up so we don't overflow the client
  1484.                 ULib.queueFunctionCall( function()
  1485.                     for j=i, math.min( i+chunk_size-1, #lines ) do
  1486.                         ULib.console( calling_ply, lines[ j ]:gsub( "%%", "<p>" ) )
  1487.                     end
  1488.                 end )
  1489.             end
  1490.         else
  1491.             ULib.console( calling_ply, "Result: " .. tostring( tmp_var ):gsub( "%%", "<p>" ) )
  1492.         end
  1493.     end
  1494.  
  1495.     ulx.fancyLogAdmin( calling_ply, true, "#A ran lua: #s", command )
  1496. end
  1497. local luarun = ulx.command( CATEGORY_NAME, "ulx luarun", ulx.luaRun, nil, false, false, true )
  1498. luarun:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
  1499. luarun:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1500. luarun:help( "Executes lua in server console. (Use '=' for output)" )
  1501.  
  1502. function ulx.exec( calling_ply, config )
  1503.     if string.sub( config, -4 ) ~= ".cfg" then config = config .. ".cfg" end
  1504.     if not ULib.fileExists( "cfg/" .. config ) then
  1505.         ULib.tsayError( calling_ply, "That config does not exist!", true )
  1506.         return
  1507.     end
  1508.  
  1509.     ULib.execFile( "cfg/" .. config )
  1510.     ulx.fancyLogAdmin( calling_ply, "#A executed file #s", config )
  1511. end
  1512. local exec = ulx.command( CATEGORY_NAME, "ulx exec", ulx.exec, nil, false, false, true )
  1513. exec:addParam{ type=ULib.cmds.StringArg, hint="file" }
  1514. exec:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1515. exec:help( "Execute a file from the cfg directory on the server." )
  1516.  
  1517. function ulx.cexec( calling_ply, target_plys, command )
  1518.     for _, v in ipairs( target_plys ) do
  1519.         v:ConCommand( command )
  1520.     end
  1521.  
  1522.     ulx.fancyLogAdmin( calling_ply, "#A ran #s on #T", command, target_plys )
  1523. end
  1524. local cexec = ulx.command( CATEGORY_NAME, "ulx cexec", ulx.cexec, "!cexec", false, false, true )
  1525. cexec:addParam{ type=ULib.cmds.PlayersArg }
  1526. cexec:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
  1527. cexec:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1528. cexec:help( "Run a command on console of target(s)." )
  1529.  
  1530. function ulx.ent( calling_ply, classname, params )
  1531.     if not calling_ply:IsValid() then
  1532.         Msg( "Can't create entities from dedicated server console.\n" )
  1533.         return
  1534.     end
  1535.  
  1536.     classname = classname:lower()
  1537.     newEnt = ents.Create( classname )
  1538.  
  1539.     -- Make sure it's a valid ent
  1540.     if not newEnt or not newEnt:IsValid() then
  1541.         ULib.tsayError( calling_ply, "Unknown entity type (" .. classname .. "), aborting.", true )
  1542.         return
  1543.     end
  1544.  
  1545.     local trace = calling_ply:GetEyeTrace()
  1546.     local vector = trace.HitPos
  1547.     vector.z = vector.z + 20
  1548.  
  1549.     newEnt:SetPos( vector ) -- Note that the position can be overridden by the user's flags
  1550.  
  1551.     params:gsub( "([^|:\"]+)\"?:\"?([^|]+)", function( key, value )
  1552.         key = key:Trim()
  1553.         value = value:Trim()
  1554.         newEnt:SetKeyValue( key, value )
  1555.     end )
  1556.  
  1557.     newEnt:Spawn()
  1558.     newEnt:Activate()
  1559.  
  1560.     undo.Create( "ulx_ent" )
  1561.         undo.AddEntity( newEnt )
  1562.         undo.SetPlayer( calling_ply )
  1563.     undo.Finish()
  1564.  
  1565.     if not params or params == "" then
  1566.         ulx.fancyLogAdmin( calling_ply, "#A created ent #s", classname )
  1567.     else
  1568.         ulx.fancyLogAdmin( calling_ply, "#A created ent #s with params #s", classname, params )
  1569.     end
  1570. end
  1571. local ent = ulx.command( CATEGORY_NAME, "ulx ent", ulx.ent, nil, false, false, true )
  1572. ent:addParam{ type=ULib.cmds.StringArg, hint="classname" }
  1573. ent:addParam{ type=ULib.cmds.StringArg, hint="<flag> : <value> |", ULib.cmds.takeRestOfLine, ULib.cmds.optional }
  1574. ent:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1575. ent:help( "Spawn an ent, separate flag and value with ':', flag:value pairs with '|'." )
  1576. CATEGORY_NAME = "Teleport"
  1577.  
  1578. local function spiralGrid(rings)
  1579.     local grid = {}
  1580.     local col, row
  1581.  
  1582.     for ring=1, rings do -- For each ring...
  1583.         row = ring
  1584.         for col=1-ring, ring do -- Walk right across top row
  1585.             table.insert( grid, {col, row} )
  1586.         end
  1587.  
  1588.         col = ring
  1589.         for row=ring-1, -ring, -1 do -- Walk down right-most column
  1590.             table.insert( grid, {col, row} )
  1591.         end
  1592.  
  1593.         row = -ring
  1594.         for col=ring-1, -ring, -1 do -- Walk left across bottom row
  1595.             table.insert( grid, {col, row} )
  1596.         end
  1597.  
  1598.         col = -ring
  1599.         for row=1-ring, ring do -- Walk up left-most column
  1600.             table.insert( grid, {col, row} )
  1601.         end
  1602.     end
  1603.  
  1604.     return grid
  1605. end
  1606. local tpGrid = spiralGrid( 24 )
  1607.  
  1608. -- Utility function for bring, goto, and send
  1609. local function playerSend( from, to, force )
  1610.     if not to:IsInWorld() and not force then return false end -- No way we can do this one
  1611.  
  1612.     local yawForward = to:EyeAngles().yaw
  1613.     local directions = { -- Directions to try
  1614.         math.NormalizeAngle( yawForward - 180 ), -- Behind first
  1615.         math.NormalizeAngle( yawForward + 90 ), -- Right
  1616.         math.NormalizeAngle( yawForward - 90 ), -- Left
  1617.         yawForward,
  1618.     }
  1619.  
  1620.     local t = {}
  1621.     t.start = to:GetPos() + Vector( 0, 0, 32 ) -- Move them up a bit so they can travel across the ground
  1622.     t.filter = { to, from }
  1623.  
  1624.     local i = 1
  1625.     t.endpos = to:GetPos() + Angle( 0, directions[ i ], 0 ):Forward() * 47 -- (33 is player width, this is sqrt( 33^2 * 2 ))
  1626.     local tr = util.TraceEntity( t, from )
  1627.     while tr.Hit do -- While it's hitting something, check other angles
  1628.         i = i + 1
  1629.         if i > #directions then  -- No place found
  1630.             if force then
  1631.                 from.ulx_prevpos = from:GetPos()
  1632.                 from.ulx_prevang = from:EyeAngles()
  1633.                 return to:GetPos() + Angle( 0, directions[ 1 ], 0 ):Forward() * 47
  1634.             else
  1635.                 return false
  1636.             end
  1637.         end
  1638.  
  1639.         t.endpos = to:GetPos() + Angle( 0, directions[ i ], 0 ):Forward() * 47
  1640.  
  1641.         tr = util.TraceEntity( t, from )
  1642.     end
  1643.  
  1644.     from.ulx_prevpos = from:GetPos()
  1645.     from.ulx_prevang = from:EyeAngles()
  1646.     return tr.HitPos
  1647. end
  1648.  
  1649. -- Based on code donated by Timmy (https://github.com/Toxsa)
  1650. function ulx.bring( calling_ply, target_plys )
  1651.     local cell_size = 50 -- Constance spacing value
  1652.  
  1653.   if not calling_ply:IsValid() then
  1654.     Msg( "If you brought someone to you, they would instantly be destroyed by the awesomeness that is console.\n" )
  1655.     return
  1656.   end
  1657.  
  1658.   if ulx.getExclusive( calling_ply, calling_ply ) then
  1659.     ULib.tsayError( calling_ply, ulx.getExclusive( calling_ply, calling_ply ), true )
  1660.     return
  1661.   end
  1662.  
  1663.   if not calling_ply:Alive() then
  1664.     ULib.tsayError( calling_ply, "You are dead!", true )
  1665.     return
  1666.   end
  1667.  
  1668.   if calling_ply:InVehicle() then
  1669.     ULib.tsayError( calling_ply, "Please leave the vehicle first!", true )
  1670.     return
  1671.   end
  1672.  
  1673.     local t = {
  1674.         start = calling_ply:GetPos(),
  1675.         filter = { calling_ply },
  1676.         endpos = calling_ply:GetPos(),
  1677.     }
  1678.     local tr = util.TraceEntity( t, calling_ply )
  1679.  
  1680.   if tr.Hit then
  1681.     ULib.tsayError( calling_ply, "Can't teleport when you're inside the world!", true )
  1682.     return
  1683.   end
  1684.  
  1685.   local teleportable_plys = {}
  1686.  
  1687.   for i=1, #target_plys do
  1688.     local v = target_plys[ i ]
  1689.     if ulx.getExclusive( v, calling_ply ) then
  1690.       ULib.tsayError( calling_ply, ulx.getExclusive( v, calling_ply ), true )
  1691.     elseif not v:Alive() then
  1692.       ULib.tsayError( calling_ply, v:Nick() .. " is dead!", true )
  1693.     else
  1694.       table.insert( teleportable_plys, v )
  1695.     end
  1696.   end
  1697.     local players_involved = table.Copy( teleportable_plys )
  1698.     table.insert( players_involved, calling_ply )
  1699.  
  1700.   local affected_plys = {}
  1701.  
  1702.   for i=1, #tpGrid do
  1703.         local c = tpGrid[i][1]
  1704.         local r = tpGrid[i][2]
  1705.     local target = table.remove( teleportable_plys )
  1706.         if not target then break end
  1707.  
  1708.         local yawForward = calling_ply:EyeAngles().yaw
  1709.         local offset = Vector( r * cell_size, c * cell_size, 0 )
  1710.         offset:Rotate( Angle( 0, yawForward, 0 ) )
  1711.  
  1712.         local t = {}
  1713.         t.start = calling_ply:GetPos() + Vector( 0, 0, 32 ) -- Move them up a bit so they can travel across the ground
  1714.         t.filter = players_involved
  1715.         t.endpos = t.start + offset
  1716.         local tr = util.TraceEntity( t, target )
  1717.  
  1718.     if tr.Hit then
  1719.       table.insert( teleportable_plys, target )
  1720.     else
  1721.       if target:InVehicle() then target:ExitVehicle() end
  1722.             target.ulx_prevpos = target:GetPos()
  1723.             target.ulx_prevang = target:EyeAngles()
  1724.       target:SetPos( t.endpos )
  1725.       target:SetEyeAngles( (calling_ply:GetPos() - t.endpos):Angle() )
  1726.       target:SetLocalVelocity( Vector( 0, 0, 0 ) )
  1727.       table.insert( affected_plys, target )
  1728.     end
  1729.   end
  1730.  
  1731.   if #teleportable_plys > 0 then
  1732.     ULib.tsayError( calling_ply, "Not enough free space to bring everyone!", true )
  1733.   end
  1734.  
  1735.     if #affected_plys > 0 then
  1736.     ulx.fancyLogAdmin( calling_ply, "#A brought #T", affected_plys )
  1737.     end
  1738. end
  1739. local bring = ulx.command( CATEGORY_NAME, "ulx bring", ulx.bring, "!bring" )
  1740. bring:addParam{ type=ULib.cmds.PlayersArg, target="!^" }
  1741. bring:defaultAccess( ULib.ACCESS_ADMIN )
  1742. bring:help( "Brings target(s) to you." )
  1743.  
  1744. function ulx.goto( calling_ply, target_ply )
  1745.     if not calling_ply:IsValid() then
  1746.         Msg( "You may not step down into the mortal world from console.\n" )
  1747.         return
  1748.     end
  1749.  
  1750.     if ulx.getExclusive( calling_ply, calling_ply ) then
  1751.         ULib.tsayError( calling_ply, ulx.getExclusive( calling_ply, calling_ply ), true )
  1752.         return
  1753.     end
  1754.  
  1755.     if not target_ply:Alive() then
  1756.         ULib.tsayError( calling_ply, target_ply:Nick() .. " is dead!", true )
  1757.         return
  1758.     end
  1759.  
  1760.     if not calling_ply:Alive() then
  1761.         ULib.tsayError( calling_ply, "You are dead!", true )
  1762.         return
  1763.     end
  1764.  
  1765.     if target_ply:InVehicle() and calling_ply:GetMoveType() ~= MOVETYPE_NOCLIP then
  1766.         ULib.tsayError( calling_ply, "Target is in a vehicle! Noclip and use this command to force a goto.", true )
  1767.         return
  1768.     end
  1769.  
  1770.     local newpos = playerSend( calling_ply, target_ply, calling_ply:GetMoveType() == MOVETYPE_NOCLIP )
  1771.     if not newpos then
  1772.         ULib.tsayError( calling_ply, "Can't find a place to put you! Noclip and use this command to force a goto.", true )
  1773.         return
  1774.     end
  1775.  
  1776.     if calling_ply:InVehicle() then
  1777.         calling_ply:ExitVehicle()
  1778.     end
  1779.  
  1780.     local newang = (target_ply:GetPos() - newpos):Angle()
  1781.  
  1782.     calling_ply:SetPos( newpos )
  1783.     calling_ply:SetEyeAngles( newang )
  1784.     calling_ply:SetLocalVelocity( Vector( 0, 0, 0 ) ) -- Stop!
  1785.  
  1786.     ulx.fancyLogAdmin( calling_ply, "#A teleported to #T", target_ply )
  1787. end
  1788. local goto = ulx.command( CATEGORY_NAME, "ulx goto", ulx.goto, "!goto" )
  1789. goto:addParam{ type=ULib.cmds.PlayerArg, target="!^", ULib.cmds.ignoreCanTarget }
  1790. goto:defaultAccess( ULib.ACCESS_ADMIN )
  1791. goto:help( "Goto target." )
  1792.  
  1793. function ulx.send( calling_ply, target_from, target_to )
  1794.     if target_from == target_to then
  1795.         ULib.tsayError( calling_ply, "You listed the same target twice! Please use two different targets.", true )
  1796.         return
  1797.     end
  1798.  
  1799.     if ulx.getExclusive( target_from, calling_ply ) then
  1800.         ULib.tsayError( calling_ply, ulx.getExclusive( target_from, calling_ply ), true )
  1801.         return
  1802.     end
  1803.  
  1804.     if ulx.getExclusive( target_to, calling_ply ) then
  1805.         ULib.tsayError( calling_ply, ulx.getExclusive( target_to, calling_ply ), true )
  1806.         return
  1807.     end
  1808.  
  1809.     local nick = target_from:Nick() -- Going to use this for our error (if we have one)
  1810.  
  1811.     if not target_from:Alive() or not target_to:Alive() then
  1812.         if not target_to:Alive() then
  1813.             nick = target_to:Nick()
  1814.         end
  1815.         ULib.tsayError( calling_ply, nick .. " is dead!", true )
  1816.         return
  1817.     end
  1818.  
  1819.     if target_to:InVehicle() and target_from:GetMoveType() ~= MOVETYPE_NOCLIP then
  1820.         ULib.tsayError( calling_ply, "Target is in a vehicle!", true )
  1821.         return
  1822.     end
  1823.  
  1824.     local newpos = playerSend( target_from, target_to, target_from:GetMoveType() == MOVETYPE_NOCLIP )
  1825.     if not newpos then
  1826.         ULib.tsayError( calling_ply, "Can't find a place to put them!", true )
  1827.         return
  1828.     end
  1829.  
  1830.     if target_from:InVehicle() then
  1831.         target_from:ExitVehicle()
  1832.     end
  1833.  
  1834.     local newang = (target_from:GetPos() - newpos):Angle()
  1835.  
  1836.     target_from:SetPos( newpos )
  1837.     target_from:SetEyeAngles( newang )
  1838.     target_from:SetLocalVelocity( Vector( 0, 0, 0 ) ) -- Stop!
  1839.  
  1840.     ulx.fancyLogAdmin( calling_ply, "#A transported #T to #T", target_from, target_to )
  1841. end
  1842. local send = ulx.command( CATEGORY_NAME, "ulx send", ulx.send, "!send" )
  1843. send:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
  1844. send:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
  1845. send:defaultAccess( ULib.ACCESS_ADMIN )
  1846. send:help( "Goto target." )
  1847.  
  1848. function ulx.teleport( calling_ply, target_ply )
  1849.     if not calling_ply:IsValid() then
  1850.         Msg( "You are the console, you can't teleport or teleport others since you can't see the world!\n" )
  1851.         return
  1852.     end
  1853.  
  1854.     if ulx.getExclusive( target_ply, calling_ply ) then
  1855.         ULib.tsayError( calling_ply, ulx.getExclusive( target_ply, calling_ply ), true )
  1856.         return
  1857.     end
  1858.  
  1859.     if not target_ply:Alive() then
  1860.         ULib.tsayError( calling_ply, target_ply:Nick() .. " is dead!", true )
  1861.         return
  1862.     end
  1863.  
  1864.     local t = {}
  1865.     t.start = calling_ply:GetPos() + Vector( 0, 0, 32 ) -- Move them up a bit so they can travel across the ground
  1866.     t.endpos = calling_ply:GetPos() + calling_ply:EyeAngles():Forward() * 16384
  1867.     t.filter = target_ply
  1868.     if target_ply ~= calling_ply then
  1869.         t.filter = { target_ply, calling_ply }
  1870.     end
  1871.     local tr = util.TraceEntity( t, target_ply )
  1872.  
  1873.     local pos = tr.HitPos
  1874.  
  1875.     if target_ply == calling_ply and pos:Distance( target_ply:GetPos() ) < 64 then -- Laughable distance
  1876.         return
  1877.     end
  1878.  
  1879.     target_ply.ulx_prevpos = target_ply:GetPos()
  1880.     target_ply.ulx_prevang = target_ply:EyeAngles()
  1881.  
  1882.     if target_ply:InVehicle() then
  1883.         target_ply:ExitVehicle()
  1884.     end
  1885.  
  1886.     target_ply:SetPos( pos )
  1887.     target_ply:SetLocalVelocity( Vector( 0, 0, 0 ) ) -- Stop!
  1888.  
  1889.     if target_ply ~= calling_ply then
  1890.         ulx.fancyLogAdmin( calling_ply, "#A teleported #T", target_ply ) -- We don't want to log otherwise
  1891.     end
  1892. end
  1893. local teleport = ulx.command( CATEGORY_NAME, "ulx teleport", ulx.teleport, {"!tp", "!teleport"} )
  1894. teleport:addParam{ type=ULib.cmds.PlayerArg, ULib.cmds.optional }
  1895. teleport:defaultAccess( ULib.ACCESS_ADMIN )
  1896. teleport:help( "Teleports target." )
  1897.  
  1898. function ulx.retrn( calling_ply, target_ply )
  1899.     if not target_ply:IsValid() then
  1900.         Msg( "Return where? The console may never return to the mortal realm.\n" )
  1901.         return
  1902.     end
  1903.  
  1904.     if not target_ply.ulx_prevpos then
  1905.         ULib.tsayError( calling_ply, target_ply:Nick() .. " does not have any previous locations to send them to.", true )
  1906.         return
  1907.     end
  1908.  
  1909.     if ulx.getExclusive( target_ply, calling_ply ) then
  1910.         ULib.tsayError( calling_ply, ulx.getExclusive( target_ply, calling_ply ), true )
  1911.         return
  1912.     end
  1913.  
  1914.     if not target_ply:Alive() then
  1915.         ULib.tsayError( calling_ply, target_ply:Nick() .. " is dead!", true )
  1916.         return
  1917.     end
  1918.  
  1919.     if target_ply:InVehicle() then
  1920.         target_ply:ExitVehicle()
  1921.     end
  1922.  
  1923.     target_ply:SetPos( target_ply.ulx_prevpos )
  1924.     target_ply:SetEyeAngles( target_ply.ulx_prevang )
  1925.     target_ply.ulx_prevpos = nil
  1926.     target_ply.ulx_prevang = nil
  1927.     target_ply:SetLocalVelocity( Vector( 0, 0, 0 ) ) -- Stop!
  1928.  
  1929.     ulx.fancyLogAdmin( calling_ply, "#A returned #T to their original position", target_ply )
  1930. end
  1931. local retrn = ulx.command( CATEGORY_NAME, "ulx return", ulx.retrn, "!return" )
  1932. retrn:addParam{ type=ULib.cmds.PlayerArg, ULib.cmds.optional }
  1933. retrn:defaultAccess( ULib.ACCESS_ADMIN )
  1934. retrn:help( "Returns target to last position before a teleport." )
  1935. local CATEGORY_NAME = "User Management"
  1936.  
  1937. local function checkForValidId( calling_ply, id )
  1938.     if id == "BOT" or id == "NULL" then -- Bot check
  1939.         return true
  1940.     elseif id:find( "%." ) then -- Assume IP and check
  1941.         if not ULib.isValidIP( id ) then
  1942.             ULib.tsayError( calling_ply, "Invalid IP.", true )
  1943.             return false
  1944.         end
  1945.     elseif id:find( ":" ) then
  1946.         if not ULib.isValidSteamID( id ) then -- Assume steamid and check
  1947.             ULib.tsayError( calling_ply, "Invalid steamid.", true )
  1948.             return false
  1949.         end
  1950.     elseif not tonumber( id ) then -- Assume uniqueid and check
  1951.         ULib.tsayError( calling_ply, "Invalid Unique ID", true )
  1952.         return false
  1953.     end
  1954.  
  1955.     return true
  1956. end
  1957.  
  1958. ulx.group_names = {}
  1959. ulx.group_names_no_user = {}
  1960. local function updateNames()
  1961.     table.Empty( ulx.group_names ) -- Don't reassign so we don't lose our refs
  1962.     table.Empty( ulx.group_names_no_user )
  1963.  
  1964.     for group_name, _ in pairs( ULib.ucl.groups ) do
  1965.         table.insert( ulx.group_names, group_name )
  1966.         if group_name ~= ULib.ACCESS_ALL then
  1967.             table.insert( ulx.group_names_no_user, group_name )
  1968.         end
  1969.     end
  1970. end
  1971. hook.Add( ULib.HOOK_UCLCHANGED, "ULXGroupNamesUpdate", updateNames )
  1972. updateNames() -- Init
  1973.  
  1974. function ulx.usermanagementhelp( calling_ply )
  1975.     if calling_ply:IsValid() then
  1976.         ULib.clientRPC( calling_ply, "ulx.showUserHelp" )
  1977.     else
  1978.         ulx.showUserHelp()
  1979.     end
  1980. end
  1981. local usermanagementhelp = ulx.command( CATEGORY_NAME, "ulx usermanagementhelp", ulx.usermanagementhelp )
  1982. usermanagementhelp:defaultAccess( ULib.ACCESS_ALL )
  1983. usermanagementhelp:help( "See the user management help." )
  1984.  
  1985. function ulx.adduser( calling_ply, target_ply, group_name )
  1986.     local userInfo = ULib.ucl.authed[ target_ply:UniqueID() ]
  1987.  
  1988.     local id = ULib.ucl.getUserRegisteredID( target_ply )
  1989.     if not id then id = target_ply:SteamID() end
  1990.  
  1991.     ULib.ucl.addUser( id, userInfo.allow, userInfo.deny, group_name )
  1992.  
  1993.     ulx.fancyLogAdmin( calling_ply, "#A added #T to group #s", target_ply, group_name )
  1994. end
  1995. local adduser = ulx.command( CATEGORY_NAME, "ulx adduser", ulx.adduser, nil, false, false, true )
  1996. adduser:addParam{ type=ULib.cmds.PlayerArg }
  1997. adduser:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  1998. adduser:defaultAccess( ULib.ACCESS_SUPERADMIN )
  1999. adduser:help( "Add a user to specified group." )
  2000.  
  2001. function ulx.adduserid( calling_ply, id, group_name )
  2002.     id = id:upper() -- Steam id needs to be upper
  2003.  
  2004.     -- Check for valid and properly formatted ID
  2005.     if not checkForValidId( calling_ply, id ) then return false end
  2006.  
  2007.     -- Now add the fool!
  2008.     ULib.ucl.addUser( id, allows, denies, group_name )
  2009.  
  2010.     if ULib.ucl.users[ id ] and ULib.ucl.users[ id ].name then
  2011.         ulx.fancyLogAdmin( calling_ply, "#A added #s to group #s", ULib.ucl.users[ id ].name, group_name )
  2012.     else
  2013.         ulx.fancyLogAdmin( calling_ply, "#A added userid #s to group #s", id, group_name )
  2014.     end
  2015. end
  2016. local adduserid = ulx.command( CATEGORY_NAME, "ulx adduserid", ulx.adduserid, nil, false, false, true )
  2017. adduserid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
  2018. adduserid:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2019. adduserid:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2020. adduserid:help( "Add a user by ID to specified group." )
  2021.  
  2022. function ulx.removeuser( calling_ply, target_ply )
  2023.     ULib.ucl.removeUser( target_ply:UniqueID() )
  2024.  
  2025.     ulx.fancyLogAdmin( calling_ply, "#A removed all access rights from #T", target_ply )
  2026. end
  2027. local removeuser = ulx.command( CATEGORY_NAME, "ulx removeuser", ulx.removeuser, nil, false, false, true )
  2028. removeuser:addParam{ type=ULib.cmds.PlayerArg }
  2029. removeuser:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2030. removeuser:help( "Permanently removes a user's access." )
  2031.  
  2032. function ulx.removeuserid( calling_ply, id )
  2033.     id = id:upper() -- Steam id needs to be upper
  2034.  
  2035.     -- Check for valid and properly formatted ID
  2036.     if not checkForValidId( calling_ply, id ) then return false end
  2037.  
  2038.     if not ULib.ucl.authed[ id ] and not ULib.ucl.users[ id ] then
  2039.         ULib.tsayError( calling_ply, "No player with id \"" .. id .. "\" exists in the ULib user list", true )
  2040.         return false
  2041.     end
  2042.  
  2043.     local name = (ULib.ucl.authed[ id ] and ULib.ucl.authed[ id ].name) or (ULib.ucl.users[ id ] and ULib.ucl.users[ id ].name)
  2044.  
  2045.     ULib.ucl.removeUser( id )
  2046.  
  2047.     if name then
  2048.         ulx.fancyLogAdmin( calling_ply, "#A removed all access rights from #s", name )
  2049.     else
  2050.         ulx.fancyLogAdmin( calling_ply, "#A removed all access rights from #s", id )
  2051.     end
  2052. end
  2053. local removeuserid = ulx.command( CATEGORY_NAME, "ulx removeuserid", ulx.removeuserid, nil, false, false, true )
  2054. removeuserid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
  2055. removeuserid:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2056. removeuserid:help( "Permanently removes a user's access by ID." )
  2057.  
  2058. function ulx.userallow( calling_ply, target_ply, access_string, access_tag )
  2059.     if access_tag then access_tag = access_tag end
  2060.  
  2061.     local accessTable
  2062.     if access_tag and access_tag ~= "" then
  2063.         accessTable = { [access_string]=access_tag }
  2064.     else
  2065.         accessTable = { access_string }
  2066.     end
  2067.  
  2068.     local id = ULib.ucl.getUserRegisteredID( target_ply )
  2069.     if not id then id = target_ply:SteamID() end
  2070.  
  2071.     local success = ULib.ucl.userAllow( id, accessTable )
  2072.     if not success then
  2073.         ULib.tsayError( calling_ply, string.format( "User \"%s\" already has access to \"%s\"", target_ply:Nick(), access_string ), true )
  2074.     else
  2075.         if not access_tag or access_tag == "" then
  2076.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q to #T", access_string, target_ply )
  2077.         else
  2078.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q with tag #q to #T", access_string, access_tag, target_ply )
  2079.         end
  2080.     end
  2081. end
  2082. local userallow = ulx.command( CATEGORY_NAME, "ulx userallow", ulx.userallow, nil, false, false, true )
  2083. userallow:addParam{ type=ULib.cmds.PlayerArg }
  2084. userallow:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2085. userallow:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
  2086. userallow:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2087. userallow:help( "Add to a user's access." )
  2088.  
  2089. function ulx.userallowid( calling_ply, id, access_string, access_tag )
  2090.     if access_tag then access_tag = access_tag end
  2091.     id = id:upper() -- Steam id needs to be upper
  2092.  
  2093.     -- Check for valid and properly formatted ID
  2094.     if not checkForValidId( calling_ply, id ) then return false end
  2095.  
  2096.     if not ULib.ucl.authed[ id ] and not ULib.ucl.users[ id ] then
  2097.         ULib.tsayError( calling_ply, "No player with id \"" .. id .. "\" exists in the ULib user list", true )
  2098.         return false
  2099.     end
  2100.  
  2101.     local accessTable
  2102.     if access_tag and access_tag ~= "" then
  2103.         accessTable = { [access_string]=access_tag }
  2104.     else
  2105.         accessTable = { access_string }
  2106.     end
  2107.  
  2108.     local success = ULib.ucl.userAllow( id, accessTable )
  2109.     local name = (ULib.ucl.authed[ id ] and ULib.ucl.authed[ id ].name) or (ULib.ucl.users[ id ] and ULib.ucl.users[ id ].name) or id
  2110.     if not success then
  2111.         ULib.tsayError( calling_ply, string.format( "User \"%s\" already has access to \"%s\"", name, access_string ), true )
  2112.     else
  2113.         if not access_tag or access_tag == "" then
  2114.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q to #s", access_string, name )
  2115.         else
  2116.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q with tag #q to #s", access_string, access_tag, name )
  2117.         end
  2118.     end
  2119. end
  2120. local userallowid = ulx.command( CATEGORY_NAME, "ulx userallowid", ulx.userallowid, nil, false, false, true )
  2121. userallowid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
  2122. userallowid:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2123. userallowid:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
  2124. userallowid:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2125. userallowid:help( "Add to a user's access." )
  2126.  
  2127. function ulx.userdeny( calling_ply, target_ply, access_string, should_use_neutral )
  2128.     local success = ULib.ucl.userAllow( target_ply:UniqueID(), access_string, should_use_neutral, true )
  2129.     if should_use_neutral then
  2130.         success = success or ULib.ucl.userAllow( target_ply:UniqueID(), access_string, should_use_neutral, false ) -- Remove from both lists
  2131.     end
  2132.  
  2133.     if should_use_neutral then
  2134.         if success then
  2135.             ulx.fancyLogAdmin( calling_ply, "#A made access #q neutral to #T", access_string, target_ply )
  2136.         else
  2137.             ULib.tsayError( calling_ply, string.format( "User \"%s\" isn't denied or allowed access to \"%s\"", target_ply:Nick(), access_string ), true )
  2138.         end
  2139.     else
  2140.         if not success then
  2141.             ULib.tsayError( calling_ply, string.format( "User \"%s\" is already denied access to \"%s\"", target_ply:Nick(), access_string ), true )
  2142.         else
  2143.             ulx.fancyLogAdmin( calling_ply, "#A denied access #q to #T", access_string, target_ply )
  2144.         end
  2145.     end
  2146. end
  2147. local userdeny = ulx.command( CATEGORY_NAME, "ulx userdeny", ulx.userdeny, nil, false, false, true )
  2148. userdeny:addParam{ type=ULib.cmds.PlayerArg }
  2149. userdeny:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2150. userdeny:addParam{ type=ULib.cmds.BoolArg, hint="remove explicit allow or deny instead of outright denying", ULib.cmds.optional }
  2151. userdeny:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2152. userdeny:help( "Remove from a user's access." )
  2153.  
  2154. function ulx.userdenyid( calling_ply, id, access_string, should_use_neutral )
  2155.     id = id:upper() -- Steam id needs to be upper
  2156.  
  2157.     -- Check for valid and properly formatted ID
  2158.     if not checkForValidId( calling_ply, id ) then return false end
  2159.  
  2160.     if not ULib.ucl.authed[ id ] and not ULib.ucl.users[ id ] then
  2161.         ULib.tsayError( calling_ply, "No player with id \"" .. id .. "\" exists in the ULib user list", true )
  2162.         return false
  2163.     end
  2164.  
  2165.     local success = ULib.ucl.userAllow( id, access_string, should_use_neutral, true )
  2166.     if should_use_neutral then
  2167.         success = success or ULib.ucl.userAllow( id, access_string, should_use_neutral, false ) -- Remove from both lists
  2168.     end
  2169.  
  2170.     local name = (ULib.ucl.authed[ id ] and ULib.ucl.authed[ id ].name) or (ULib.ucl.users[ id ] and ULib.ucl.users[ id ].name) or id
  2171.     if should_use_neutral then
  2172.         if success then
  2173.             ulx.fancyLogAdmin( calling_ply, "#A made access #q neutral to #s", access_string, name )
  2174.         else
  2175.             ULib.tsayError( calling_ply, string.format( "User \"%s\" isn't denied or allowed access to \"%s\"", name, access_string ), true )
  2176.         end
  2177.     else
  2178.         if not success then
  2179.             ULib.tsayError( calling_ply, string.format( "User \"%s\" is already denied access to \"%s\"", name, access_string ), true )
  2180.         else
  2181.             ulx.fancyLogAdmin( calling_ply, "#A denied access #q to #s", access_string, name )
  2182.         end
  2183.     end
  2184. end
  2185. local userdenyid = ulx.command( CATEGORY_NAME, "ulx userdenyid", ulx.userdenyid, nil, false, false, true )
  2186. userdenyid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
  2187. userdenyid:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2188. userdenyid:addParam{ type=ULib.cmds.BoolArg, hint="remove explicit allow or deny instead of outright denying", ULib.cmds.optional }
  2189. userdenyid:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2190. userdenyid:help( "Remove from a user's access." )
  2191.  
  2192. function ulx.addgroup( calling_ply, group_name, inherit_from )
  2193.     if ULib.ucl.groups[ group_name ] ~= nil then
  2194.         ULib.tsayError( calling_ply, "This group already exists!", true )
  2195.         return
  2196.     end
  2197.  
  2198.     if not ULib.ucl.groups[ inherit_from ] then
  2199.         ULib.tsayError( calling_ply, "The group you specified for inheritence doesn't exist!", true )
  2200.         return
  2201.     end
  2202.  
  2203.     ULib.ucl.addGroup( group_name, _, inherit_from )
  2204.     ulx.fancyLogAdmin( calling_ply, "#A created group #s which inherits rights from group #s", group_name, inherit_from )
  2205. end
  2206. local addgroup = ulx.command( CATEGORY_NAME, "ulx addgroup", ulx.addgroup, nil, false, false, true )
  2207. addgroup:addParam{ type=ULib.cmds.StringArg, hint="group" }
  2208. addgroup:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="inherits from", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes, default="user", ULib.cmds.optional }
  2209. addgroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2210. addgroup:help( "Create a new group with optional inheritance." )
  2211.  
  2212. function ulx.renamegroup( calling_ply, current_group, new_group )
  2213.     if ULib.ucl.groups[ new_group ] then
  2214.         ULib.tsayError( calling_ply, "The target group already exists!", true )
  2215.         return
  2216.     end
  2217.  
  2218.     ULib.ucl.renameGroup( current_group, new_group )
  2219.     ulx.fancyLogAdmin( calling_ply, "#A renamed group #s to #s", current_group, new_group )
  2220. end
  2221. local renamegroup = ulx.command( CATEGORY_NAME, "ulx renamegroup", ulx.renamegroup, nil, false, false, true )
  2222. renamegroup:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="current group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2223. renamegroup:addParam{ type=ULib.cmds.StringArg, hint="new group" }
  2224. renamegroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2225. renamegroup:help( "Renames a group." )
  2226.  
  2227. function ulx.setGroupCanTarget( calling_ply, group, can_target )
  2228.     if can_target and can_target ~= "" and can_target ~= "*" then
  2229.         ULib.ucl.setGroupCanTarget( group, can_target )
  2230.         ulx.fancyLogAdmin( calling_ply, "#A changed group #s to only be able to target #s", group, can_target )
  2231.     else
  2232.         ULib.ucl.setGroupCanTarget( group, nil )
  2233.         ulx.fancyLogAdmin( calling_ply, "#A changed group #s to be able to target anyone", group )
  2234.     end
  2235. end
  2236. local setgroupcantarget = ulx.command( CATEGORY_NAME, "ulx setgroupcantarget", ulx.setGroupCanTarget, nil, false, false, true )
  2237. setgroupcantarget:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2238. setgroupcantarget:addParam{ type=ULib.cmds.StringArg, hint="target string", ULib.cmds.optional }
  2239. setgroupcantarget:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2240. setgroupcantarget:help( "Sets what a group is allowed to target" )
  2241.  
  2242. function ulx.removegroup( calling_ply, group_name )
  2243.     ULib.ucl.removeGroup( group_name )
  2244.     ulx.fancyLogAdmin( calling_ply, "#A removed group #s", group_name )
  2245. end
  2246. local removegroup = ulx.command( CATEGORY_NAME, "ulx removegroup", ulx.removegroup, nil, false, false, true )
  2247. removegroup:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2248. removegroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2249. removegroup:help( "Removes a group. USE WITH CAUTION." )
  2250.  
  2251. function ulx.groupallow( calling_ply, group_name, access_string, access_tag )
  2252.     access_tag = access_tag
  2253.  
  2254.     local accessTable
  2255.     if access_tag and access_tag ~= "" then
  2256.         accessTable = { [access_string]=access_tag }
  2257.     else
  2258.         accessTable = { access_string }
  2259.     end
  2260.  
  2261.     local success = ULib.ucl.groupAllow( group_name, accessTable )
  2262.     if not success then
  2263.         ULib.tsayError( calling_ply, string.format( "Group \"%s\" already has access to \"%s\"", group_name, access_string ), true )
  2264.     else
  2265.         if not access_tag or access_tag == "" then
  2266.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q to group #s", access_string, group_name )
  2267.         else
  2268.             ulx.fancyLogAdmin( calling_ply, "#A granted access #q with tag #q to group #s", access_string, access_tag, group_name )
  2269.         end
  2270.     end
  2271. end
  2272. local groupallow = ulx.command( CATEGORY_NAME, "ulx groupallow", ulx.groupallow, nil, false, false, true )
  2273. groupallow:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2274. groupallow:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2275. groupallow:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
  2276. groupallow:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2277. groupallow:help( "Add to a group's access." )
  2278.  
  2279. function ulx.groupdeny( calling_ply, group_name, access_string )
  2280.     local accessTable
  2281.     if access_tag and access_tag ~= "" then
  2282.         accessTable = { [access_string]=access_tag }
  2283.     else
  2284.         accessTable = { access_string }
  2285.     end
  2286.  
  2287.     local success = ULib.ucl.groupAllow( group_name, access_string, true )
  2288.     if success then
  2289.         ulx.fancyLogAdmin( calling_ply, "#A revoked access #q to group #s", access_string, group_name )
  2290.     else
  2291.         ULib.tsayError( calling_ply, string.format( "Group \"%s\" doesn't have access to \"%s\"", group_name, access_string ), true )
  2292.     end
  2293. end
  2294. local groupdeny = ulx.command( CATEGORY_NAME, "ulx groupdeny", ulx.groupdeny, nil, false, false, true )
  2295. groupdeny:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
  2296. groupdeny:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
  2297. groupdeny:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2298. groupdeny:help( "Remove from a group's access." )
  2299.  
  2300. local help = [[
  2301. General User Management Concepts:
  2302. User access is driven by ULib's Ulysses Control List (UCL). This list contains users and groups
  2303. which in turn contains lists of allowed and denied accesses. The allow and deny lists contain
  2304. access strings like "ulx slap" or "ulx phygunplayer" to show what a user and/or group does and does
  2305. not have access to. If a user has "ulx slap" in their user allow list or in the allow list of one
  2306. of the groups they belong to, they have access to slap. If a user has "ulx slap" in their user deny
  2307. list they are DENIED the command, even if they have the command in one of their group's allow
  2308. lists. In this way, deny takes precedence over allow.
  2309.  
  2310. ULib supports immunity by being able to specify what various users and groups are allowed to
  2311. target. This is often used to make it so lower admins cannot target higher admins. EG, by default
  2312. admins can't target superadmins, but superadmins can still target admins.
  2313.  
  2314.  
  2315. More Advanced Concepts:
  2316. Groups have inheritance. You can specify what group they inherit from in the addgroup command. If a
  2317. user is in a group that has inheritance, UCL will check all groups connected in the inheritance
  2318. chain. Note that groups do not support deny lists for the sake of simplicity. If you feel that a
  2319. group needs to be denied something, you should split your groups up instead.
  2320.  
  2321. The "user" group applies to everyone who does not otherwise belong in a group. You can use
  2322. groupallow on this group just like any other, just remember that everyone is being allowed access.
  2323.  
  2324. ULib supports an advanced, highly configurable permission system by using "access tags". Access
  2325. tags specify what a user is allowed to pass as arguments to a command. For example, you can make it
  2326. so that admins are only allowed to slay users with "killme" somewhere in their name, or you can
  2327. give everyone access to the "ulx teleport" command, but only allow them to teleport themselves.
  2328.  
  2329. Examples of using access tags are given below in the userallow and groupallow commands. The format
  2330. for access tags is as follows. Each argument that is passed to the command can be limited by the
  2331. access tag. Each argument being limited must be listed in the same order as in the command,
  2332. separated by spaces. If you don't want to limit an argument, use a star ("*"). EG, to limit "ulx
  2333. slap" damage to 0 through 10, but still allow it to be used on anyone, use the tag "* 0:10".
  2334.  
  2335. User Management Commands:
  2336. ulx adduser <user> <group> - Add the specified CONNECTED player to the specified group.
  2337. The group MUST exist for this command to succeed. Use operator, admin, superadmin, or see ulx
  2338. addgroup. You can only specify one group. See above for explanation on immunity.
  2339. Ex 1. ulx adduser "Someguy" superadmin  -- This will add the connected "Someguy" as a superadmin
  2340. Ex 2. ulx adduser "Dood" monkey         -- This will add the connected "Dood" to the group monkey
  2341.   on the condition that the group exists
  2342.  
  2343. ulx removeuser <user> - Remove the specified connected player from the permanent access list.
  2344. Ex 1. ulx removeuser "Foo bar"            -- This removes the user "Foo bar"
  2345.  
  2346. ulx userallow <user> <access> [<access tag>] - Puts the access on the USER'S ALLOW list, with
  2347.  optional access tag (see above)
  2348. See above for explanation of allow list vs. deny list, as well as how access strings/tags work.
  2349. Ex 1. ulx userallow "Pi" "ulx slap"                 -- This grants the user access to "ulx slap"
  2350. Ex 2. ulx userallow "Pi" "ulx slap" "!%admin 0"     -- This grants the user access to "ulx slap"
  2351.  -- but they can only slap users lower than an admin, and they can only slap for 0 damage
  2352.  
  2353. ulx userdeny <user> <access> [<revoke>] - Removes a player's access. If revoke is true, this simply
  2354.   removes the access string from the user's allow/deny lists instead of adding it to the user's
  2355.   deny list. See above for an explanation on the deny list.
  2356.  
  2357. ulx addgroup <group> [<inherits from>] - Creates a group, optionally inheriting from the specified
  2358.   group. See above for explanation on inheritance.
  2359.  
  2360. ulx removegroup <group> - Removes a group PERMANENTLY. Also removes the group from all connected
  2361.   users and all users who connect in the future. If a user has no group besides this, they will
  2362.   become guests. Please be VERY careful with this command!
  2363.  
  2364. ulx renamegroup <current group> <new group> - Renames a group
  2365.  
  2366. ulx setgroupcantarget <group> [<target string>] - Limits what users a group can target. Pass no
  2367.   argument to clear the restriction.
  2368. Ex 1. ulx setgroupcantarget user !%admin - Guests cannot target admins or above
  2369. Ex 2. ulx setgroupcantarget admin !^ - Admins cannot target themselves
  2370.  
  2371. ulx groupallow <group> <access> [<access tag>] - Puts the access on the group's allow list. See
  2372.  above on how access strings/tags work.
  2373.  
  2374. ulx groupdeny <group> <access> - Removes the group's access.
  2375.  
  2376.  
  2377. ]]
  2378.  
  2379. function ulx.showUserHelp()
  2380.     local lines = ULib.explode( "\n", help )
  2381.     for _, line in ipairs( lines ) do
  2382.         Msg( line .. "\n" )
  2383.     end
  2384. end
  2385. local CATEGORY_NAME = "Utility"
  2386.  
  2387. ------------------------------ Who ------------------------------
  2388. function ulx.who( calling_ply, steamid )
  2389.     if not steamid or steamid == "" then
  2390.         ULib.console( calling_ply, "ID Name                            Group" )
  2391.  
  2392.         local players = player.GetAll()
  2393.         for _, player in ipairs( players ) do
  2394.             local id = tostring( player:UserID() )
  2395.             local nick = utf8.force( player:Nick() )
  2396.             local text = string.format( "%i%s %s%s ", id, string.rep( " ", 2 - id:len() ), nick, string.rep( " ", 31 - utf8.len( nick ) ) )
  2397.  
  2398.             text = text .. player:GetUserGroup()
  2399.  
  2400.             ULib.console( calling_ply, text )
  2401.         end
  2402.     else
  2403.         data = ULib.ucl.getUserInfoFromID( steamid )
  2404.  
  2405.         if not data then
  2406.             ULib.console( calling_ply, "No information for provided id exists" )
  2407.         else
  2408.             ULib.console( calling_ply, "   ID: " .. steamid )
  2409.             ULib.console( calling_ply, " Name: " .. data.name )
  2410.             ULib.console( calling_ply, "Group: " .. data.group )
  2411.         end
  2412.  
  2413.  
  2414.     end
  2415. end
  2416. local who = ulx.command( CATEGORY_NAME, "ulx who", ulx.who )
  2417. who:addParam{ type=ULib.cmds.StringArg, hint="steamid", ULib.cmds.optional }
  2418. who:defaultAccess( ULib.ACCESS_ALL )
  2419. who:help( "See information about currently online users." )
  2420.  
  2421. ------------------------------ Version ------------------------------
  2422. function ulx.versionCmd( calling_ply )
  2423.     ULib.tsay( calling_ply, "ULib " .. ULib.pluginVersionStr("ULib"), true )
  2424.     ULib.tsay( calling_ply, "ULX " .. ULib.pluginVersionStr("ULX"), true )
  2425. end
  2426. local version = ulx.command( CATEGORY_NAME, "ulx version", ulx.versionCmd, "!version" )
  2427. version:defaultAccess( ULib.ACCESS_ALL )
  2428. version:help( "See version information." )
  2429.  
  2430. ------------------------------ Map ------------------------------
  2431. function ulx.map( calling_ply, map, gamemode )
  2432.     if not gamemode or gamemode == "" then
  2433.         ulx.fancyLogAdmin( calling_ply, "#A changed the map to #s", map )
  2434.     else
  2435.         ulx.fancyLogAdmin( calling_ply, "#A changed the map to #s with gamemode #s", map, gamemode )
  2436.     end
  2437.     if gamemode and gamemode ~= "" then
  2438.         game.ConsoleCommand( "gamemode " .. gamemode .. "\n" )
  2439.     end
  2440.     game.ConsoleCommand( "changelevel " .. map ..  "\n" )
  2441. end
  2442. local map = ulx.command( CATEGORY_NAME, "ulx map", ulx.map, "!map" )
  2443. map:addParam{ type=ULib.cmds.StringArg, completes=ulx.maps, hint="map", error="invalid map \"%s\" specified", ULib.cmds.restrictToCompletes }
  2444. map:addParam{ type=ULib.cmds.StringArg, completes=ulx.gamemodes, hint="gamemode", error="invalid gamemode \"%s\" specified", ULib.cmds.restrictToCompletes, ULib.cmds.optional }
  2445. map:defaultAccess( ULib.ACCESS_ADMIN )
  2446. map:help( "Changes map and gamemode." )
  2447.  
  2448. function ulx.kick( calling_ply, target_ply, reason )
  2449.     if target_ply:IsListenServerHost() then
  2450.         ULib.tsayError( calling_ply, "This player is immune to kicking", true )
  2451.         return
  2452.     end
  2453.  
  2454.     if reason and reason ~= "" then
  2455.         ulx.fancyLogAdmin( calling_ply, "#A kicked #T (#s)", target_ply, reason )
  2456.     else
  2457.         reason = nil
  2458.         ulx.fancyLogAdmin( calling_ply, "#A kicked #T", target_ply )
  2459.     end
  2460.     -- Delay by 1 frame to ensure the chat hook finishes with player intact. Prevents a crash.
  2461.     ULib.queueFunctionCall( ULib.kick, target_ply, reason, calling_ply )
  2462. end
  2463. local kick = ulx.command( CATEGORY_NAME, "ulx kick", ulx.kick, "!kick" )
  2464. kick:addParam{ type=ULib.cmds.PlayerArg }
  2465. kick:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
  2466. kick:defaultAccess( ULib.ACCESS_ADMIN )
  2467. kick:help( "Kicks target." )
  2468.  
  2469. ------------------------------ Ban ------------------------------
  2470. function ulx.ban( calling_ply, target_ply, minutes, reason )
  2471.     if target_ply:IsListenServerHost() or target_ply:IsBot() then
  2472.         ULib.tsayError( calling_ply, "This player is immune to banning", true )
  2473.         return
  2474.     end
  2475.  
  2476.     local time = "for #s"
  2477.     if minutes == 0 then time = "permanently" end
  2478.     local str = "#A banned #T " .. time
  2479.     if reason and reason ~= "" then str = str .. " (#s)" end
  2480.     ulx.fancyLogAdmin( calling_ply, str, target_ply, minutes ~= 0 and ULib.secondsToStringTime( minutes * 60 ) or reason, reason )
  2481.     -- Delay by 1 frame to ensure any chat hook finishes with player intact. Prevents a crash.
  2482.     ULib.queueFunctionCall( ULib.kickban, target_ply, minutes, reason, calling_ply )
  2483. end
  2484. local ban = ulx.command( CATEGORY_NAME, "ulx ban", ulx.ban, "!ban", false, false, true )
  2485. ban:addParam{ type=ULib.cmds.PlayerArg }
  2486. ban:addParam{ type=ULib.cmds.NumArg, hint="minutes, 0 for perma", ULib.cmds.optional, ULib.cmds.allowTimeString, min=0 }
  2487. ban:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
  2488. ban:defaultAccess( ULib.ACCESS_ADMIN )
  2489. ban:help( "Bans target." )
  2490.  
  2491. ------------------------------ BanID ------------------------------
  2492. function ulx.banid( calling_ply, steamid, minutes, reason )
  2493.     steamid = steamid:upper()
  2494.     if not ULib.isValidSteamID( steamid ) then
  2495.         ULib.tsayError( calling_ply, "Invalid steamid." )
  2496.         return
  2497.     end
  2498.  
  2499.     local name, target_ply
  2500.     local plys = player.GetAll()
  2501.     for i=1, #plys do
  2502.         if plys[ i ]:SteamID() == steamid then
  2503.             target_ply = plys[ i ]
  2504.             name = target_ply:Nick()
  2505.             break
  2506.         end
  2507.     end
  2508.  
  2509.     if target_ply and (target_ply:IsListenServerHost() or target_ply:IsBot()) then
  2510.         ULib.tsayError( calling_ply, "This player is immune to banning", true )
  2511.         return
  2512.     end
  2513.  
  2514.     local time = "for #s"
  2515.     if minutes == 0 then time = "permanently" end
  2516.     local str = "#A banned steamid #s "
  2517.     displayid = steamid
  2518.     if name then
  2519.         displayid = displayid .. "(" .. name .. ") "
  2520.     end
  2521.     str = str .. time
  2522.     if reason and reason ~= "" then str = str .. " (#4s)" end
  2523.     ulx.fancyLogAdmin( calling_ply, str, displayid, minutes ~= 0 and ULib.secondsToStringTime( minutes * 60 ) or reason, reason )
  2524.     -- Delay by 1 frame to ensure any chat hook finishes with player intact. Prevents a crash.
  2525.     ULib.queueFunctionCall( ULib.addBan, steamid, minutes, reason, name, calling_ply )
  2526. end
  2527. local banid = ulx.command( CATEGORY_NAME, "ulx banid", ulx.banid, nil, false, false, true )
  2528. banid:addParam{ type=ULib.cmds.StringArg, hint="steamid" }
  2529. banid:addParam{ type=ULib.cmds.NumArg, hint="minutes, 0 for perma", ULib.cmds.optional, ULib.cmds.allowTimeString, min=0 }
  2530. banid:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
  2531. banid:defaultAccess( ULib.ACCESS_SUPERADMIN )
  2532. banid:help( "Bans steamid." )
  2533.  
  2534. function ulx.unban( calling_ply, steamid )
  2535.     steamid = steamid:upper()
  2536.     if not ULib.isValidSteamID( steamid ) then
  2537.         ULib.tsayError( calling_ply, "Invalid steamid." )
  2538.         return
  2539.     end
  2540.  
  2541.     name = ULib.bans[ steamid ] and ULib.bans[ steamid ].name
  2542.  
  2543.     ULib.unban( steamid, calling_ply )
  2544.     if name then
  2545.         ulx.fancyLogAdmin( calling_ply, "#A unbanned steamid #s", steamid .. " (" .. name .. ")" )
  2546.     else
  2547.         ulx.fancyLogAdmin( calling_ply, "#A unbanned steamid #s", steamid )
  2548.     end
  2549. end
  2550. local unban = ulx.command( CATEGORY_NAME, "ulx unban", ulx.unban, nil, false, false, true )
  2551. unban:addParam{ type=ULib.cmds.StringArg, hint="steamid" }
  2552. unban:defaultAccess( ULib.ACCESS_ADMIN )
  2553. unban:help( "Unbans steamid." )
  2554.  
  2555. ------------------------------ Noclip ------------------------------
  2556. function ulx.noclip( calling_ply, target_plys )
  2557.     if not target_plys[ 1 ]:IsValid() then
  2558.         Msg( "You are god, you are not constrained by walls built by mere mortals.\n" )
  2559.         return
  2560.     end
  2561.  
  2562.     local affected_plys = {}
  2563.     for i=1, #target_plys do
  2564.         local v = target_plys[ i ]
  2565.  
  2566.         if v.NoNoclip then
  2567.             ULib.tsayError( calling_ply, v:Nick() .. " can't be noclipped right now.", true )
  2568.         else
  2569.             if v:GetMoveType() == MOVETYPE_WALK then
  2570.                 v:SetMoveType( MOVETYPE_NOCLIP )
  2571.                 table.insert( affected_plys, v )
  2572.             elseif v:GetMoveType() == MOVETYPE_NOCLIP then
  2573.                 v:SetMoveType( MOVETYPE_WALK )
  2574.                 table.insert( affected_plys, v )
  2575.             else -- Ignore if they're an observer
  2576.                 ULib.tsayError( calling_ply, v:Nick() .. " can't be noclipped right now.", true )
  2577.             end
  2578.         end
  2579.     end
  2580. end
  2581. local noclip = ulx.command( CATEGORY_NAME, "ulx noclip", ulx.noclip, "!noclip" )
  2582. noclip:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
  2583. noclip:defaultAccess( ULib.ACCESS_ADMIN )
  2584. noclip:help( "Toggles noclip on target(s)." )
  2585.  
  2586. function ulx.spectate( calling_ply, target_ply )
  2587.     if not calling_ply:IsValid() then
  2588.         Msg( "You can't spectate from dedicated server console.\n" )
  2589.         return
  2590.     end
  2591.  
  2592.     -- Check if player is already spectating. If so, stop spectating so we can start again
  2593.     local hookTable = hook.GetTable()["KeyPress"]
  2594.     if hookTable and hookTable["ulx_unspectate_" .. calling_ply:EntIndex()] then
  2595.         -- Simulate keypress to properly exit spectate.
  2596.         hook.Call( "KeyPress", _, calling_ply, IN_FORWARD )
  2597.     end
  2598.  
  2599.     if ulx.getExclusive( calling_ply, calling_ply ) then
  2600.         ULib.tsayError( calling_ply, ulx.getExclusive( calling_ply, calling_ply ), true )
  2601.         return
  2602.     end
  2603.  
  2604.     ULib.getSpawnInfo( calling_ply )
  2605.  
  2606.     local pos = calling_ply:GetPos()
  2607.     local ang = calling_ply:GetAngles()
  2608.  
  2609.     local wasAlive = calling_ply:Alive()
  2610.  
  2611.     local function stopSpectate( player )
  2612.         if player ~= calling_ply then -- For the spawning, make sure it's them doing the spawning
  2613.             return
  2614.         end
  2615.  
  2616.         hook.Remove( "PlayerSpawn", "ulx_unspectatedspawn_" .. calling_ply:EntIndex() )
  2617.         hook.Remove( "KeyPress", "ulx_unspectate_" .. calling_ply:EntIndex() )
  2618.         hook.Remove( "PlayerDisconnected", "ulx_unspectatedisconnect_" .. calling_ply:EntIndex() )
  2619.  
  2620.         if player.ULXHasGod then player:GodEnable() end -- Restore if player had ulx god.
  2621.         player:UnSpectate() -- Need this for DarkRP for some reason, works fine without it in sbox
  2622.         ulx.fancyLogAdmin( calling_ply, true, "#A stopped spectating #T", target_ply )
  2623.         ulx.clearExclusive( calling_ply )
  2624.     end
  2625.     hook.Add( "PlayerSpawn", "ulx_unspectatedspawn_" .. calling_ply:EntIndex(), stopSpectate, HOOK_MONITOR_HIGH )
  2626.  
  2627.     local function unspectate( player, key )
  2628.         if calling_ply ~= player then return end -- Not the person we want
  2629.         if key ~= IN_FORWARD and key ~= IN_BACK and key ~= IN_MOVELEFT and key ~= IN_MOVERIGHT then return end -- Not a key we're interested in
  2630.  
  2631.         hook.Remove( "PlayerSpawn", "ulx_unspectatedspawn_" .. calling_ply:EntIndex() ) -- Otherwise spawn would cause infinite loop
  2632.         if wasAlive then -- We don't want to spawn them if they were already dead.
  2633.             ULib.spawn( player, true ) -- Get out of spectate.
  2634.         end
  2635.         stopSpectate( player )
  2636.         player:SetPos( pos )
  2637.         player:SetAngles( ang )
  2638.     end
  2639.     hook.Add( "KeyPress", "ulx_unspectate_" .. calling_ply:EntIndex(), unspectate, HOOK_MONITOR_LOW )
  2640.  
  2641.     local function disconnect( player ) -- We want to watch for spectator or target disconnect
  2642.         if player == target_ply or player == calling_ply then -- Target or spectator disconnecting
  2643.             unspectate( calling_ply, IN_FORWARD )
  2644.         end
  2645.     end
  2646.     hook.Add( "PlayerDisconnected", "ulx_unspectatedisconnect_" .. calling_ply:EntIndex(), disconnect, HOOK_MONITOR_HIGH )
  2647.  
  2648.     calling_ply:Spectate( OBS_MODE_IN_EYE )
  2649.     calling_ply:SpectateEntity( target_ply )
  2650.     calling_ply:StripWeapons() -- Otherwise they can use weapons while spectating
  2651.  
  2652.     ULib.tsay( calling_ply, "To get out of spectate, move forward.", true )
  2653.     ulx.setExclusive( calling_ply, "spectating" )
  2654.  
  2655.     ulx.fancyLogAdmin( calling_ply, true, "#A began spectating #T", target_ply )
  2656. end
  2657. local spectate = ulx.command( CATEGORY_NAME, "ulx spectate", ulx.spectate, "!spectate", true )
  2658. spectate:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
  2659. spectate:defaultAccess( ULib.ACCESS_ADMIN )
  2660. spectate:help( "Spectate target." )
  2661.  
  2662. function ulx.addForcedDownload( path )
  2663.     if ULib.fileIsDir( path ) then
  2664.         files = ULib.filesInDir( path )
  2665.         for _, v in ipairs( files ) do
  2666.             ulx.addForcedDownload( path .. "/" .. v )
  2667.         end
  2668.     elseif ULib.fileExists( path ) then
  2669.         resource.AddFile( path )
  2670.     else
  2671.         Msg( "[ULX] ERROR: Tried to add nonexistent or empty file to forced downloads '" .. path .. "'\n" )
  2672.     end
  2673. end
  2674.  
  2675. function ulx.debuginfo( calling_ply )
  2676.     local str = string.format( "ULX version: %s\nULib version: %s\n", ULib.pluginVersionStr( "ULX" ), ULib.pluginVersionStr( "ULib" ) )
  2677.     str = str .. string.format( "Gamemode: %s\nMap: %s\n", GAMEMODE.Name, game.GetMap() )
  2678.     str = str .. "Dedicated server: " .. tostring( game.IsDedicated() ) .. "\n\n"
  2679.  
  2680.     local players = player.GetAll()
  2681.     str = str .. string.format( "Currently connected players:\nNick%s steamid%s uid%s id lsh\n", str.rep( " ", 27 ), str.rep( " ", 12 ), str.rep( " ", 7 ) )
  2682.     for _, ply in ipairs( players ) do
  2683.         local id = string.format( "%i", ply:EntIndex() )
  2684.         local steamid = ply:SteamID()
  2685.         local uid = tostring( ply:UniqueID() )
  2686.         local name = utf8.force( ply:Nick() )
  2687.  
  2688.         local plyline = name .. str.rep( " ", 32 - utf8.len( name ) ) -- Name
  2689.         plyline = plyline .. steamid .. str.rep( " ", 20 - steamid:len() ) -- Steamid
  2690.         plyline = plyline .. uid .. str.rep( " ", 11 - uid:len() ) -- Steamid
  2691.         plyline = plyline .. id .. str.rep( " ", 3 - id:len() ) -- id
  2692.         if ply:IsListenServerHost() then
  2693.             plyline = plyline .. "y   "
  2694.         else
  2695.             plyline = plyline .. "n   "
  2696.         end
  2697.  
  2698.         str = str .. plyline .. "\n"
  2699.     end
  2700.  
  2701.     local gmoddefault = ULib.parseKeyValues( ULib.stripComments( ULib.fileRead( "settings/users.txt", true ), "//" ) ) or {}
  2702.     str = str .. "\n\nULib.ucl.users (#=" .. table.Count( ULib.ucl.users ) .. "):\n" .. ulx.dumpTable( ULib.ucl.users, 1 ) .. "\n\n"
  2703.     str = str .. "ULib.ucl.groups (#=" .. table.Count( ULib.ucl.groups ) .. "):\n" .. ulx.dumpTable( ULib.ucl.groups, 1 ) .. "\n\n"
  2704.     str = str .. "ULib.ucl.authed (#=" .. table.Count( ULib.ucl.authed ) .. "):\n" .. ulx.dumpTable( ULib.ucl.authed, 1 ) .. "\n\n"
  2705.     str = str .. "Garrysmod default file (#=" .. table.Count( gmoddefault ) .. "):\n" .. ulx.dumpTable( gmoddefault, 1 ) .. "\n\n"
  2706.  
  2707.     str = str .. "Active workshop addons on this server:\n"
  2708.     local addons = engine.GetAddons()
  2709.     for i=1, #addons do
  2710.         local addon = addons[i]
  2711.         if addon.mounted then
  2712.             local name = utf8.force( addon.title )
  2713.             str = str .. string.format( "%s%s workshop ID %s\n", name, str.rep( " ", 32 - utf8.len( name ) ), addon.file:gsub( "%D", "" ) )
  2714.         end
  2715.     end
  2716.     str = str .. "\n"
  2717.  
  2718.     str = str .. "Active legacy addons on this server:\n"
  2719.     local _, possibleaddons = file.Find( "addons/*", "GAME" )
  2720.     for _, addon in ipairs( possibleaddons ) do
  2721.         if not ULib.findInTable( {"checkers", "chess", "common", "go", "hearts", "spades"}, addon:lower() ) then -- Not sure what these addon folders are
  2722.             local name = addon
  2723.             local author, version, date
  2724.             if ULib.fileExists( "addons/" .. addon .. "/addon.txt" ) then
  2725.                 local t = ULib.parseKeyValues( ULib.stripComments( ULib.fileRead( "addons/" .. addon .. "/addon.txt" ), "//" ) )
  2726.                 if t and t.AddonInfo then
  2727.                     t = t.AddonInfo
  2728.                     if t.name then name = t.name end
  2729.                     if t.version then version = t.version end
  2730.                     if tonumber( version ) then version = string.format( "%g", version ) end -- Removes innaccuracy in floating point numbers
  2731.                     if t.author_name then author = t.author_name end
  2732.                     if t.up_date then date = t.up_date end
  2733.                 end
  2734.             end
  2735.  
  2736.             name = utf8.force( name )
  2737.             str = str .. name .. str.rep( " ", 32 - utf8.len( name ) )
  2738.             if author then
  2739.                 str = string.format( "%s by %s%s", str, author, version and "," or "" )
  2740.             end
  2741.  
  2742.             if version then
  2743.                 str = str .. " version " .. version
  2744.             end
  2745.  
  2746.             if date then
  2747.                 str = string.format( "%s (%s)", str, date )
  2748.             end
  2749.             str = str .. "\n"
  2750.         end
  2751.     end
  2752.  
  2753.     ULib.fileWrite( "data/ulx/debugdump.txt", str )
  2754.     Msg( "Debug information written to garrysmod/data/ulx/debugdump.txt on server.\n" )
  2755. end
  2756. local debuginfo = ulx.command( CATEGORY_NAME, "ulx debuginfo", ulx.debuginfo )
  2757. debuginfo:help( "Dump some debug information." )
  2758.  
  2759. function ulx.resettodefaults( calling_ply, param )
  2760.     if param ~= "FORCE" then
  2761.         local str = "Are you SURE about this? It will remove ulx-created temporary bans, configs, groups, EVERYTHING!"
  2762.         local str2 = "If you're sure, type \"ulx resettodefaults FORCE\""
  2763.         if calling_ply:IsValid() then
  2764.             ULib.tsayError( calling_ply, str, true )
  2765.             ULib.tsayError( calling_ply, str2, true )
  2766.         else
  2767.             Msg( str .. "\n" )
  2768.             Msg( str2 .. "\n" )
  2769.         end
  2770.         return
  2771.     end
  2772.  
  2773.     ULib.fileDelete( "data/ulx/adverts.txt" )
  2774.     ULib.fileDelete( "data/ulx/banreasons.txt" )
  2775.     ULib.fileDelete( "data/ulx/config.txt" )
  2776.     ULib.fileDelete( "data/ulx/downloads.txt" )
  2777.     ULib.fileDelete( "data/ulx/gimps.txt" )
  2778.     ULib.fileDelete( "data/ulx/sbox_limits.txt" )
  2779.     ULib.fileDelete( "data/ulx/votemaps.txt" )
  2780.     ULib.fileDelete( "data/ulib/bans.txt" )
  2781.     ULib.fileDelete( "data/ulib/groups.txt" )
  2782.     ULib.fileDelete( "data/ulib/misc_registered.txt" )
  2783.     ULib.fileDelete( "data/ulib/users.txt" )
  2784.  
  2785.     local str = "Please change levels to finish the reset"
  2786.     if calling_ply:IsValid() then
  2787.         ULib.tsayError( calling_ply, str, true )
  2788.     else
  2789.         Msg( str .. "\n" )
  2790.     end
  2791.  
  2792.     ulx.fancyLogAdmin( calling_ply, "#A reset all ULX and ULib configuration" )
  2793. end
  2794. local resettodefaults = ulx.command( CATEGORY_NAME, "ulx resettodefaults", ulx.resettodefaults )
  2795. resettodefaults:addParam{ type=ULib.cmds.StringArg, ULib.cmds.optional }
  2796. resettodefaults:help( "Resets ALL ULX and ULib configuration!" )
  2797.  
  2798. if SERVER then
  2799.     local ulx_kickAfterNameChanges =            ulx.convar( "kickAfterNameChanges", "0", "<number> - Players can only change their name x times every ulx_kickAfterNameChangesCooldown seconds. 0 to disable.", ULib.ACCESS_ADMIN )
  2800.     local ulx_kickAfterNameChangesCooldown =    ulx.convar( "kickAfterNameChangesCooldown", "60", "<time> - Players can change their name ulx_kickAfterXNameChanges times every x seconds.", ULib.ACCESS_ADMIN )
  2801.     local ulx_kickAfterNameChangesWarning =     ulx.convar( "kickAfterNameChangesWarning", "1", "<1/0> - Display a warning to users to let them know how many more times they can change their name.", ULib.ACCESS_ADMIN )
  2802.     ulx.nameChangeTable = ulx.nameChangeTable or {}
  2803.  
  2804.     local function checkNameChangeLimit( ply, oldname, newname )
  2805.         local maxAttempts = ulx_kickAfterNameChanges:GetInt()
  2806.         local duration = ulx_kickAfterNameChangesCooldown:GetInt()
  2807.         local showWarning = ulx_kickAfterNameChangesWarning:GetInt()
  2808.  
  2809.         if maxAttempts ~= 0 then
  2810.             if not ulx.nameChangeTable[ply:SteamID()] then
  2811.                 ulx.nameChangeTable[ply:SteamID()] = {}
  2812.             end
  2813.  
  2814.             for i=#ulx.nameChangeTable[ply:SteamID()], 1, -1 do
  2815.                 if CurTime() - ulx.nameChangeTable[ply:SteamID()][i] > duration then
  2816.                     table.remove( ulx.nameChangeTable[ply:SteamID()], i )
  2817.                 end
  2818.             end
  2819.  
  2820.             table.insert( ulx.nameChangeTable[ply:SteamID()], CurTime() )
  2821.  
  2822.             local curAttempts = #ulx.nameChangeTable[ply:SteamID()]
  2823.  
  2824.             if curAttempts >= maxAttempts then
  2825.                 ULib.kick( ply, "Changed name too many times" )
  2826.             else
  2827.                 if showWarning == 1 then
  2828.                     ULib.tsay( ply, "Warning: You have changed your name " .. curAttempts .. " out of " .. maxAttempts .. " time" .. ( maxAttempts ~= 1 and "s" ) .. " in the past " .. duration .. " second" .. ( duration ~= 1 and "s" ) )
  2829.                 end
  2830.             end
  2831.         end
  2832.     end
  2833.     hook.Add( "ULibPlayerNameChanged", "ULXCheckNameChangeLimit", checkNameChangeLimit )
  2834. end
  2835.  
  2836. --------------------
  2837. --     Hooks      --
  2838. --------------------
  2839. -- This cvar also exists in DarkRP (thanks, FPtje)
  2840. local cl_cvar_pickup = "cl_pickupplayers"
  2841. if CLIENT then CreateClientConVar( cl_cvar_pickup, "1", true, true ) end
  2842. local function playerPickup( ply, ent )
  2843.     local access, tag = ULib.ucl.query( ply, "ulx physgunplayer" )
  2844.     if ent:GetClass() == "player" and ULib.isSandbox() and access and not ent.NoNoclip and not ent.frozen and ply:GetInfoNum( cl_cvar_pickup, 1 ) == 1 then
  2845.         -- Extra restrictions! UCL wasn't designed to handle this sort of thing so we're putting it in by hand...
  2846.         local restrictions = {}
  2847.         ULib.cmds.PlayerArg.processRestrictions( restrictions, ply, {}, tag and ULib.splitArgs( tag )[ 1 ] )
  2848.         if restrictions.restrictedTargets == false or (restrictions.restrictedTargets and not table.HasValue( restrictions.restrictedTargets, ent )) then
  2849.             return
  2850.         end
  2851.  
  2852.         ent:SetMoveType( MOVETYPE_NONE ) -- So they don't bounce
  2853.         return true
  2854.     end
  2855. end
  2856. hook.Add( "PhysgunPickup", "ulxPlayerPickup", playerPickup, HOOK_HIGH ) -- Allow admins to move players. Call before the prop protection hook.
  2857. if SERVER then ULib.ucl.registerAccess( "ulx physgunplayer", ULib.ACCESS_ADMIN, "Ability to physgun other players", "Other" ) end
  2858.  
  2859. local function playerDrop( ply, ent )
  2860.     if ent:GetClass() == "player" then
  2861.         ent:SetMoveType( MOVETYPE_WALK )
  2862.     end
  2863. end
  2864. hook.Add( "PhysgunDrop", "ulxPlayerDrop", playerDrop )
  2865.  
  2866. local CATEGORY_NAME = "Voting"
  2867.  
  2868. ---------------
  2869. --Public vote--
  2870. ---------------
  2871. if SERVER then ulx.convar( "voteEcho", "0", _, ULib.ACCESS_SUPERADMIN ) end -- Echo votes?
  2872.  
  2873. -- First, our helper function to make voting so much easier!
  2874. function ulx.doVote( title, options, callback, timeout, filter, noecho, ... )
  2875.     timeout = timeout or 20
  2876.     if ulx.voteInProgress then
  2877.         Msg( "Error! ULX tried to start a vote when another vote was in progress!\n" )
  2878.         return false
  2879.     end
  2880.  
  2881.     if not options[ 1 ] or not options[ 2 ] then
  2882.         Msg( "Error! ULX tried to start a vote without at least two options!\n" )
  2883.         return false
  2884.     end
  2885.  
  2886.     local voters = 0
  2887.     local rp = RecipientFilter()
  2888.     if not filter then
  2889.         rp:AddAllPlayers()
  2890.         voters = #player.GetAll()
  2891.     else
  2892.         for _, ply in ipairs( filter ) do
  2893.             rp:AddPlayer( ply )
  2894.             voters = voters + 1
  2895.         end
  2896.     end
  2897.  
  2898.     umsg.Start( "ulx_vote", rp )
  2899.         umsg.String( title )
  2900.         umsg.Short( timeout )
  2901.         ULib.umsgSend( options )
  2902.     umsg.End()
  2903.  
  2904.     ulx.voteInProgress = { callback=callback, options=options, title=title, results={}, voters=voters, votes=0, noecho=noecho, args={...} }
  2905.  
  2906.     timer.Create( "ULXVoteTimeout", timeout, 1, ulx.voteDone )
  2907.  
  2908.     return true
  2909. end
  2910.  
  2911. function ulx.voteCallback( ply, command, argv )
  2912.     if not ulx.voteInProgress then
  2913.         ULib.tsayError( ply, "There is not a vote in progress" )
  2914.         return
  2915.     end
  2916.  
  2917.     if not argv[ 1 ] or not tonumber( argv[ 1 ] ) or not ulx.voteInProgress.options[ tonumber( argv[ 1 ] ) ] then
  2918.         ULib.tsayError( ply, "Invalid or out of range vote." )
  2919.         return
  2920.     end
  2921.  
  2922.     if ply.ulxVoted then
  2923.         ULib.tsayError( ply, "You have already voted!" )
  2924.         return
  2925.     end
  2926.  
  2927.     local echo = ULib.toBool( GetConVarNumber( "ulx_voteEcho" ) )
  2928.     local id = tonumber( argv[ 1 ] )
  2929.     ulx.voteInProgress.results[ id ] = ulx.voteInProgress.results[ id ] or 0
  2930.     ulx.voteInProgress.results[ id ] = ulx.voteInProgress.results[ id ] + 1
  2931.  
  2932.     ulx.voteInProgress.votes = ulx.voteInProgress.votes + 1
  2933.  
  2934.     ply.ulxVoted = true -- Tag them as having voted
  2935.  
  2936.     local str = ply:Nick() .. " voted for: " .. ulx.voteInProgress.options[ id ]
  2937.     if echo and not ulx.voteInProgress.noecho then
  2938.         ULib.tsay( _, str ) -- TODO, color?
  2939.     end
  2940.     ulx.logString( str )
  2941.     if game.IsDedicated() then Msg( str .. "\n" ) end
  2942.  
  2943.     if ulx.voteInProgress.votes >= ulx.voteInProgress.voters then
  2944.         ulx.voteDone()
  2945.     end
  2946. end
  2947. if SERVER then concommand.Add( "ulx_vote", ulx.voteCallback ) end
  2948.  
  2949. function ulx.voteDone( cancelled )
  2950.     local players = player.GetAll()
  2951.     for _, ply in ipairs( players ) do -- Clear voting tags
  2952.         ply.ulxVoted = nil
  2953.     end
  2954.  
  2955.     local vip = ulx.voteInProgress
  2956.     ulx.voteInProgress = nil
  2957.     timer.Remove( "ULXVoteTimeout" )
  2958.     if not cancelled then
  2959.         ULib.pcallError( vip.callback, vip, unpack( vip.args, 1, 10 ) ) -- Unpack is explicit in length to avoid odd LuaJIT quirk.
  2960.     end
  2961. end
  2962. -- End our helper functions
  2963.  
  2964.  
  2965.  
  2966.  
  2967.  
  2968. local function voteDone( t )
  2969.     local results = t.results
  2970.     local winner
  2971.     local winnernum = 0
  2972.     for id, numvotes in pairs( results ) do
  2973.         if numvotes > winnernum then
  2974.             winner = id
  2975.             winnernum = numvotes
  2976.         end
  2977.     end
  2978.  
  2979.     local str
  2980.     if not winner then
  2981.         str = "Vote results: No option won because no one voted!"
  2982.     else
  2983.         str = "Vote results: Option '" .. t.options[ winner ] .. "' won. (" .. winnernum .. "/" .. t.voters .. ")"
  2984.     end
  2985.     ULib.tsay( _, str ) -- TODO, color?
  2986.     ulx.logString( str )
  2987.     Msg( str .. "\n" )
  2988. end
  2989.  
  2990. function ulx.vote( calling_ply, title, ... )
  2991.     if ulx.voteInProgress then
  2992.         ULib.tsayError( calling_ply, "There is already a vote in progress. Please wait for the current one to end.", true )
  2993.         return
  2994.     end
  2995.  
  2996.     ulx.doVote( title, { ... }, voteDone )
  2997.     ulx.fancyLogAdmin( calling_ply, "#A started a vote (#s)", title )
  2998. end
  2999. local vote = ulx.command( CATEGORY_NAME, "ulx vote", ulx.vote, "!vote" )
  3000. vote:addParam{ type=ULib.cmds.StringArg, hint="title" }
  3001. vote:addParam{ type=ULib.cmds.StringArg, hint="options", ULib.cmds.takeRestOfLine, repeat_min=2, repeat_max=10 }
  3002. vote:defaultAccess( ULib.ACCESS_ADMIN )
  3003. vote:help( "Starts a public vote." )
  3004.  
  3005. -- Stop a vote in progress
  3006. function ulx.stopVote( calling_ply )
  3007.     if not ulx.voteInProgress then
  3008.         ULib.tsayError( calling_ply, "There is no vote currently in progress.", true )
  3009.         return
  3010.     end
  3011.  
  3012.     ulx.voteDone( true )
  3013.     ulx.fancyLogAdmin( calling_ply, "#A has stopped the current vote." )
  3014. end
  3015. local stopvote = ulx.command( CATEGORY_NAME, "ulx stopvote", ulx.stopVote, "!stopvote" )
  3016. stopvote:defaultAccess( ULib.ACCESS_SUPERADMIN )
  3017. stopvote:help( "Stops a vote in progress." )
  3018.  
  3019. local function voteMapDone2( t, changeTo, ply )
  3020.     local shouldChange = false
  3021.  
  3022.     if t.results[ 1 ] and t.results[ 1 ] > 0 then
  3023.         ulx.logServAct( ply, "#A approved the votemap" )
  3024.         shouldChange = true
  3025.     else
  3026.         ulx.logServAct( ply, "#A denied the votemap" )
  3027.     end
  3028.  
  3029.     if shouldChange then
  3030.         ULib.consoleCommand( "changelevel " .. changeTo .. "\n" )
  3031.     end
  3032. end
  3033.  
  3034. local function voteMapDone( t, argv, ply )
  3035.     local results = t.results
  3036.     local winner
  3037.     local winnernum = 0
  3038.     for id, numvotes in pairs( results ) do
  3039.         if numvotes > winnernum then
  3040.             winner = id
  3041.             winnernum = numvotes
  3042.         end
  3043.     end
  3044.  
  3045.     local ratioNeeded = GetConVarNumber( "ulx_votemap2Successratio" )
  3046.     local minVotes = GetConVarNumber( "ulx_votemap2Minvotes" )
  3047.     local str
  3048.     local changeTo
  3049.     -- Figure out the map to change to, if we're changing
  3050.     if #argv > 1 then
  3051.         changeTo = t.options[ winner ]
  3052.     else
  3053.         changeTo = argv[ 1 ]
  3054.     end
  3055.  
  3056.     if (#argv < 2 and winner ~= 1) or not winner or winnernum < minVotes or winnernum / t.voters < ratioNeeded then
  3057.         str = "Vote results: Vote was unsuccessful."
  3058.     elseif ply:IsValid() then
  3059.         str = "Vote results: Option '" .. t.options[ winner ] .. "' won, changemap pending approval. (" .. winnernum .. "/" .. t.voters .. ")"
  3060.  
  3061.         ulx.doVote( "Accept result and changemap to " .. changeTo .. "?", { "Yes", "No" }, voteMapDone2, 30000, { ply }, true, changeTo, ply )
  3062.     else -- It's the server console, let's roll with it
  3063.         str = "Vote results: Option '" .. t.options[ winner ] .. "' won. (" .. winnernum .. "/" .. t.voters .. ")"
  3064.         ULib.tsay( _, str )
  3065.         ulx.logString( str )
  3066.         ULib.consoleCommand( "changelevel " .. changeTo .. "\n" )
  3067.         return
  3068.     end
  3069.  
  3070.     ULib.tsay( _, str ) -- TODO, color?
  3071.     ulx.logString( str )
  3072.     if game.IsDedicated() then Msg( str .. "\n" ) end
  3073. end
  3074.  
  3075. function ulx.votemap2( calling_ply, ... )
  3076.     local argv = { ... }
  3077.  
  3078.     if ulx.voteInProgress then
  3079.         ULib.tsayError( calling_ply, "There is already a vote in progress. Please wait for the current one to end.", true )
  3080.         return
  3081.     end
  3082.  
  3083.     for i=2, #argv do
  3084.         if ULib.findInTable( argv, argv[ i ], 1, i-1 ) then
  3085.             ULib.tsayError( calling_ply, "Map " .. argv[ i ] .. " was listed twice. Please try again" )
  3086.             return
  3087.         end
  3088.     end
  3089.  
  3090.     if #argv > 1 then
  3091.         ulx.doVote( "Change map to..", argv, voteMapDone, _, _, _, argv, calling_ply )
  3092.         ulx.fancyLogAdmin( calling_ply, "#A started a votemap with options" .. string.rep( " #s", #argv ), ... )
  3093.     else
  3094.         ulx.doVote( "Change map to " .. argv[ 1 ] .. "?", { "Yes", "No" }, voteMapDone, _, _, _, argv, calling_ply )
  3095.         ulx.fancyLogAdmin( calling_ply, "#A started a votemap for #s", argv[ 1 ] )
  3096.     end
  3097. end
  3098. local votemap2 = ulx.command( CATEGORY_NAME, "ulx votemap2", ulx.votemap2, "!votemap2" )
  3099. votemap2:addParam{ type=ULib.cmds.StringArg, completes=ulx.maps, hint="map", error="invalid map \"%s\" specified", ULib.cmds.restrictToCompletes, ULib.cmds.takeRestOfLine, repeat_min=1, repeat_max=10 }
  3100. votemap2:defaultAccess( ULib.ACCESS_ADMIN )
  3101. votemap2:help( "Starts a public map vote." )
  3102. if SERVER then ulx.convar( "votemap2Successratio", "0.5", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a votemap2 to succeed
  3103. if SERVER then ulx.convar( "votemap2Minvotes", "3", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for votemap2
  3104.  
  3105.  
  3106.  
  3107. local function voteKickDone2( t, target, time, ply, reason )
  3108.     local shouldKick = false
  3109.  
  3110.     if t.results[ 1 ] and t.results[ 1 ] > 0 then
  3111.         ulx.logUserAct( ply, target, "#A approved the votekick against #T (" .. (reason or "") .. ")" )
  3112.         shouldKick = true
  3113.     else
  3114.         ulx.logUserAct( ply, target, "#A denied the votekick against #T" )
  3115.     end
  3116.  
  3117.     if shouldKick then
  3118.         if reason and reason ~= "" then
  3119.             ULib.kick( target, "Vote kick successful. (" .. reason .. ")" )
  3120.         else
  3121.             ULib.kick( target, "Vote kick successful." )
  3122.         end
  3123.     end
  3124. end
  3125.  
  3126. local function voteKickDone( t, target, time, ply, reason )
  3127.     local results = t.results
  3128.     local winner
  3129.     local winnernum = 0
  3130.     for id, numvotes in pairs( results ) do
  3131.         if numvotes > winnernum then
  3132.             winner = id
  3133.             winnernum = numvotes
  3134.         end
  3135.     end
  3136.  
  3137.     local ratioNeeded = GetConVarNumber( "ulx_votekickSuccessratio" )
  3138.     local minVotes = GetConVarNumber( "ulx_votekickMinvotes" )
  3139.     local str
  3140.     if winner ~= 1 or winnernum < minVotes or winnernum / t.voters < ratioNeeded then
  3141.         str = "Vote results: User will not be kicked. (" .. (results[ 1 ] or "0") .. "/" .. t.voters .. ")"
  3142.     else
  3143.         if not target:IsValid() then
  3144.             str = "Vote results: User voted to be kicked, but has already left."
  3145.         elseif ply:IsValid() then
  3146.             str = "Vote results: User will now be kicked, pending approval. (" .. winnernum .. "/" .. t.voters .. ")"
  3147.             ulx.doVote( "Accept result and kick " .. target:Nick() .. "?", { "Yes", "No" }, voteKickDone2, 30000, { ply }, true, target, time, ply, reason )
  3148.         else -- Vote from server console, roll with it
  3149.             str = "Vote results: User will now be kicked. (" .. winnernum .. "/" .. t.voters .. ")"
  3150.             ULib.kick( target, "Vote kick successful." )
  3151.         end
  3152.     end
  3153.  
  3154.     ULib.tsay( _, str ) -- TODO, color?
  3155.     ulx.logString( str )
  3156.     if game.IsDedicated() then Msg( str .. "\n" ) end
  3157. end
  3158.  
  3159. function ulx.votekick( calling_ply, target_ply, reason )
  3160.     if target_ply:IsListenServerHost() then
  3161.         ULib.tsayError( calling_ply, "This player is immune to kicking", true )
  3162.         return
  3163.     end
  3164.  
  3165.     if ulx.voteInProgress then
  3166.         ULib.tsayError( calling_ply, "There is already a vote in progress. Please wait for the current one to end.", true )
  3167.         return
  3168.     end
  3169.  
  3170.     local msg = "Kick " .. target_ply:Nick() .. "?"
  3171.     if reason and reason ~= "" then
  3172.         msg = msg .. " (" .. reason .. ")"
  3173.     end
  3174.  
  3175.     ulx.doVote( msg, { "Yes", "No" }, voteKickDone, _, _, _, target_ply, time, calling_ply, reason )
  3176.     if reason and reason ~= "" then
  3177.         ulx.fancyLogAdmin( calling_ply, "#A started a votekick against #T (#s)", target_ply, reason )
  3178.     else
  3179.         ulx.fancyLogAdmin( calling_ply, "#A started a votekick against #T", target_ply )
  3180.     end
  3181. end
  3182. local votekick = ulx.command( CATEGORY_NAME, "ulx votekick", ulx.votekick, "!votekick" )
  3183. votekick:addParam{ type=ULib.cmds.PlayerArg }
  3184. votekick:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
  3185. votekick:defaultAccess( ULib.ACCESS_ADMIN )
  3186. votekick:help( "Starts a public kick vote against target." )
  3187. if SERVER then ulx.convar( "votekickSuccessratio", "0.6", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a votekick to succeed
  3188. if SERVER then ulx.convar( "votekickMinvotes", "2", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for votekick
  3189.  
  3190.  
  3191.  
  3192. local function voteBanDone2( t, nick, steamid, time, ply, reason )
  3193.     local shouldBan = false
  3194.  
  3195.     if t.results[ 1 ] and t.results[ 1 ] > 0 then
  3196.         ulx.fancyLogAdmin( ply, "#A approved the voteban against #s (#s minutes) (#s))", nick, time, reason or "" )
  3197.         shouldBan = true
  3198.     else
  3199.         ulx.fancyLogAdmin( ply, "#A denied the voteban against #s", nick )
  3200.     end
  3201.  
  3202.     if shouldBan then
  3203.         ULib.addBan( steamid, time, reason, nick, ply )
  3204.     end
  3205. end
  3206.  
  3207. local function voteBanDone( t, nick, steamid, time, ply, reason )
  3208.     local results = t.results
  3209.     local winner
  3210.     local winnernum = 0
  3211.     for id, numvotes in pairs( results ) do
  3212.         if numvotes > winnernum then
  3213.             winner = id
  3214.             winnernum = numvotes
  3215.         end
  3216.     end
  3217.  
  3218.     local ratioNeeded = GetConVarNumber( "ulx_votebanSuccessratio" )
  3219.     local minVotes = GetConVarNumber( "ulx_votebanMinvotes" )
  3220.     local str
  3221.     if winner ~= 1 or winnernum < minVotes or winnernum / t.voters < ratioNeeded then
  3222.         str = "Vote results: User will not be banned. (" .. (results[ 1 ] or "0") .. "/" .. t.voters .. ")"
  3223.     else
  3224.         reason = ("[ULX Voteban] " .. (reason or "")):Trim()
  3225.         if ply:IsValid() then
  3226.             str = "Vote results: User will now be banned, pending approval. (" .. winnernum .. "/" .. t.voters .. ")"
  3227.             ulx.doVote( "Accept result and ban " .. nick .. "?", { "Yes", "No" }, voteBanDone2, 30000, { ply }, true, nick, steamid, time, ply, reason )
  3228.         else -- Vote from server console, roll with it
  3229.             str = "Vote results: User will now be banned. (" .. winnernum .. "/" .. t.voters .. ")"
  3230.             ULib.addBan( steamid, time, reason, nick, ply )
  3231.         end
  3232.     end
  3233.  
  3234.     ULib.tsay( _, str ) -- TODO, color?
  3235.     ulx.logString( str )
  3236.     Msg( str .. "\n" )
  3237. end
  3238.  
  3239. function ulx.voteban( calling_ply, target_ply, minutes, reason )
  3240.     if target_ply:IsListenServerHost() or target_ply:IsBot() then
  3241.         ULib.tsayError( calling_ply, "This player is immune to banning", true )
  3242.         return
  3243.     end
  3244.  
  3245.     if ulx.voteInProgress then
  3246.         ULib.tsayError( calling_ply, "There is already a vote in progress. Please wait for the current one to end.", true )
  3247.         return
  3248.     end
  3249.  
  3250.     local msg = "Ban " .. target_ply:Nick() .. " for " .. minutes .. " minutes?"
  3251.     if reason and reason ~= "" then
  3252.         msg = msg .. " (" .. reason .. ")"
  3253.     end
  3254.  
  3255.     ulx.doVote( msg, { "Yes", "No" }, voteBanDone, _, _, _, target_ply:Nick(), target_ply:SteamID(), minutes, calling_ply, reason )
  3256.     if reason and reason ~= "" then
  3257.         ulx.fancyLogAdmin( calling_ply, "#A started a voteban of #i minute(s) against #T (#s)", minutes, target_ply, reason )
  3258.     else
  3259.         ulx.fancyLogAdmin( calling_ply, "#A started a voteban of #i minute(s) against #T", minutes, target_ply )
  3260.     end
  3261. end
  3262. local voteban = ulx.command( CATEGORY_NAME, "ulx voteban", ulx.voteban, "!voteban" )
  3263. voteban:addParam{ type=ULib.cmds.PlayerArg }
  3264. voteban:addParam{ type=ULib.cmds.NumArg, min=0, default=1440, hint="minutes", ULib.cmds.allowTimeString, ULib.cmds.optional }
  3265. voteban:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
  3266. voteban:defaultAccess( ULib.ACCESS_ADMIN )
  3267. voteban:help( "Starts a public ban vote against target." )
  3268. if SERVER then ulx.convar( "votebanSuccessratio", "0.7", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a voteban to succeed
  3269. if SERVER then ulx.convar( "votebanMinvotes", "3", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for voteban
  3270.  
  3271. -- Our regular votemap command
  3272. local votemap = ulx.command( CATEGORY_NAME, "ulx votemap", ulx.votemap, "!votemap" )
  3273. votemap:addParam{ type=ULib.cmds.StringArg, completes=ulx.votemaps, hint="map", ULib.cmds.takeRestOfLine, ULib.cmds.optional }
  3274. votemap:defaultAccess( ULib.ACCESS_ALL )
  3275. votemap:help( "Vote for a map, no args lists available maps." )
  3276.  
  3277. -- Our veto command
  3278. local veto = ulx.command( CATEGORY_NAME, "ulx veto", ulx.votemapVeto, "!veto" )
  3279. veto:defaultAccess( ULib.ACCESS_ADMIN )
  3280. veto:help( "Veto a successful votemap." )
  3281.  
  3282. --XLIB -- by Stickly Man!
  3283. --A library of helper functions used by XGUI for creating derma controls with a single line of code.
  3284.  
  3285. --Currently a bit disorganized and unstandardized, (just put in things as I needed them). I'm hoping to fix that sometime.
  3286.  
  3287. xlib = {}
  3288.  
  3289. function xlib.makecheckbox( t )
  3290.     local pnl = vgui.Create( "DCheckBoxLabel", t.parent )
  3291.     pnl:SetPos( t.x, t.y )
  3292.     pnl:SetText( t.label or "" )
  3293.     pnl:SizeToContents()
  3294.     pnl:SetValue( t.value or 0 )
  3295.     pnl:SetZPos( t.zpos or 0 )
  3296.     if t.convar then pnl:SetConVar( t.convar ) end
  3297.  
  3298.     if t.textcolor then
  3299.         pnl:SetTextColor( t.textcolor )
  3300.     else
  3301.         pnl:SetTextColor( SKIN.text_dark )
  3302.     end
  3303.  
  3304.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3305.     if t.tooltip then
  3306.         if t.tooltipwidth ~= 0 then
  3307.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3308.         end
  3309.         pnl:SetTooltip( t.tooltip )
  3310.     end
  3311.  
  3312.     function pnl:SetDisabled( val )
  3313.         pnl.disabled = val
  3314.         pnl:SetMouseInputEnabled( not val )
  3315.         pnl:SetAlpha( val and 128 or 255 )
  3316.     end
  3317.     if t.disabled then pnl:SetDisabled( t.disabled ) end
  3318.  
  3319.     --Work around for bug where changing the parent of a disabled textbox reenables mouse input.
  3320.     local tempfunc = pnl.SetParent
  3321.     pnl.SetParent = function( self, parent )
  3322.         local ret = tempfunc( self, parent )
  3323.         self:SetDisabled( self.disabled )
  3324.         return ret
  3325.     end
  3326.  
  3327.     --Replicated Convar Updating
  3328.     if t.repconvar then
  3329.         xlib.checkRepCvarCreated( t.repconvar )
  3330.         pnl:SetValue( GetConVar( t.repconvar ):GetBool() )
  3331.         function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3332.             if cl_cvar == t.repconvar:lower() then
  3333.                 pnl:SetValue( new_val )
  3334.             end
  3335.         end
  3336.         hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3337.         function pnl:OnChange( bVal )
  3338.             RunConsoleCommand( t.repconvar, tostring( bVal and 1 or 0 ) )
  3339.         end
  3340.         pnl.Think = function() end --Override think functions to remove Garry's convar check to (hopefully) speed things up
  3341.         pnl.ConVarNumberThink = function() end
  3342.         pnl.ConVarStringThink = function() end
  3343.         pnl.ConVarChanged = function() end
  3344.     end
  3345.     return pnl
  3346. end
  3347.  
  3348. function xlib.makelabel( t )
  3349.     local pnl = vgui.Create( "DLabel", t.parent )
  3350.     pnl:SetPos( t.x, t.y )
  3351.     pnl:SetText( t.label or "" )
  3352.     pnl:SetZPos( t.zpos or 0 )
  3353.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3354.     if t.tooltip then
  3355.         if t.tooltipwidth ~= 0 then
  3356.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3357.         end
  3358.         pnl:SetTooltip( t.tooltip )
  3359.         pnl:SetMouseInputEnabled( true )
  3360.     end
  3361.  
  3362.     if t.font then pnl:SetFont( t.font ) end
  3363.     if t.w and t.wordwrap then
  3364.         pnl:SetText( xlib.wordWrap( t.label, t.w, t.font or "Default" ) )
  3365.     end
  3366.     pnl:SizeToContents()
  3367.     if t.w then pnl:SetWidth( t.w ) end
  3368.     if t.h then pnl:SetHeight( t.h ) end
  3369.     if t.textcolor then
  3370.         pnl:SetTextColor( t.textcolor )
  3371.     else
  3372.         pnl:SetTextColor( SKIN.text_dark )
  3373.     end
  3374.  
  3375.     return pnl
  3376. end
  3377.  
  3378. function xlib.makelistlayout( t )
  3379.     local pnl = vgui.Create( "DListLayout" )
  3380.     pnl.scroll = vgui.Create( "DScrollPanel", t.parent )
  3381.  
  3382.     pnl.scroll:SetPos( t.x, t.y )
  3383.     pnl.scroll:SetSize( t.w, t.h )
  3384.     pnl:SetSize( t.w, t.h )
  3385.     pnl.scroll:AddItem( pnl )
  3386.     pnl:SetZPos( t.zpos or 0 )
  3387.  
  3388.     function pnl:PerformLayout()
  3389.         self:SizeToChildren( false, true )
  3390.         self:SetWide( self.scroll:GetWide() - ( self.scroll.VBar.Enabled and 16 or 0 ) )
  3391.     end
  3392.     return pnl
  3393. end
  3394.  
  3395. function xlib.makebutton( t )
  3396.     local pnl = vgui.Create( "DButton", t.parent )
  3397.     pnl:SetSize( t.w or 20, t.h or 20 )
  3398.     pnl:SetPos( t.x, t.y )
  3399.     pnl:SetText( t.label or "" )
  3400.     pnl:SetDisabled( t.disabled )
  3401.     pnl:SetZPos( t.zpos or 0 )
  3402.     if t.icon then pnl:SetIcon( t.icon ) end
  3403.     if t.font then pnl:SetFont( t.font ) end
  3404.     if t.btype and t.btype == "close" then
  3405.         pnl.Paint = function( panel, w, h ) derma.SkinHook( "Paint", "WindowCloseButton", panel, w, h ) end
  3406.     end
  3407.     if t.centericon then    --Place the image in the cetner of the button instead of the default layout.
  3408.         function pnl:PerformLayout()
  3409.             if ( IsValid( self.m_Image ) ) then
  3410.                 self.m_Image:SetPos( (self:GetWide() - self.m_Image:GetWide()) * 0.5, (self:GetTall() - self.m_Image:GetTall()) * 0.5 )
  3411.                 self:SetTextInset( self.m_Image:GetWide() + 16, 0 )
  3412.             end
  3413.             DLabel.PerformLayout( self )
  3414.         end
  3415.     end
  3416.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3417.     if t.tooltip then
  3418.         if t.tooltipwidth ~= 0 then
  3419.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3420.         end
  3421.         pnl:SetTooltip( t.tooltip )
  3422.         pnl:SetMouseInputEnabled( true )
  3423.     end
  3424.  
  3425.     return pnl
  3426. end
  3427.  
  3428. function xlib.makeframe( t )
  3429.     local pnl = vgui.Create( "DFrame", t.parent )
  3430.     pnl:SetSize( t.w, t.h )
  3431.     if t.nopopup ~= true then pnl:MakePopup() end
  3432.     pnl:SetPos( t.x or ScrW()/2-t.w/2, t.y or ScrH()/2-t.h/2 )
  3433.     pnl:SetTitle( t.label or "" )
  3434.     if t.draggable ~= nil then pnl:SetDraggable( t.draggable ) end
  3435.     if t.showclose ~= nil then pnl:ShowCloseButton( t.showclose ) end
  3436.     if t.skin then pnl:SetSkin( t.skin ) end
  3437.     if t.visible ~= nil then pnl:SetVisible( t.visible ) end
  3438.     return pnl
  3439. end
  3440.  
  3441. function xlib.makepropertysheet( t )
  3442.     local pnl = vgui.Create( "DPropertySheet", t.parent )
  3443.     pnl:SetPos( t.x, t.y )
  3444.     pnl:SetSize( t.w, t.h )
  3445.     --Clears all of the tabs in the base.
  3446.     function pnl:Clear()
  3447.         for _, Sheet in ipairs( self.Items ) do
  3448.             Sheet.Panel:SetParent( t.offloadparent )
  3449.             Sheet.Tab:Remove()
  3450.         end
  3451.         self.m_pActiveTab = nil
  3452.         self:SetActiveTab( nil )
  3453.         self.tabScroller.Panels = {}
  3454.         self.Items = {}
  3455.     end
  3456.     return pnl
  3457. end
  3458.  
  3459. function xlib.maketextbox( t )
  3460.     local pnl = vgui.Create( "DTextEntry", t.parent )
  3461.     pnl:SetPos( t.x, t.y )
  3462.     pnl:SetWide( t.w )
  3463.     pnl:SetTall( t.h or 20 )
  3464.     pnl:SetEnterAllowed( true )
  3465.     pnl:SetZPos( t.zpos or 0 )
  3466.     if t.convar then pnl:SetConVar( t.convar ) end
  3467.     if t.text then pnl:SetText( t.text ) end
  3468.     if t.enableinput then pnl:SetEnabled( t.enableinput ) end
  3469.     if t.multiline then pnl:SetMultiline( t.multiline ) end
  3470.     pnl.selectAll = t.selectall
  3471.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3472.     if t.tooltip then
  3473.         if t.tooltipwidth ~= 0 then
  3474.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3475.         end
  3476.         pnl:SetTooltip( t.tooltip )
  3477.     end
  3478.  
  3479.     function pnl:SetDisabled( val ) --Simulate enabling/disabling of a textbox
  3480.         pnl:SetEnabled( not val )
  3481.         pnl:SetMouseInputEnabled( not val )
  3482.         pnl:SetAlpha( val and 128 or 255 )
  3483.     end
  3484.     if t.disabled then pnl:SetDisabled( t.disabled ) end
  3485.  
  3486.     --Replicated Convar Updating
  3487.     if t.repconvar then
  3488.         xlib.checkRepCvarCreated( t.repconvar )
  3489.         pnl:SetValue( GetConVar( t.repconvar ):GetString() )
  3490.         function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3491.             if cl_cvar == t.repconvar:lower() then
  3492.                 pnl:SetValue( new_val )
  3493.             end
  3494.         end
  3495.         hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3496.         function pnl:UpdateConvarValue()
  3497.             RunConsoleCommand( t.repconvar, self:GetValue() )
  3498.         end
  3499.         function pnl:OnEnter()
  3500.             RunConsoleCommand( t.repconvar, self:GetValue() )
  3501.         end
  3502.         pnl.Think = function() end --Override think functions to remove Garry's convar check to (hopefully) speed things up
  3503.         pnl.ConVarNumberThink = function() end
  3504.         pnl.ConVarStringThink = function() end
  3505.         pnl.ConVarChanged = function() end
  3506.     end
  3507.     return pnl
  3508. end
  3509.  
  3510. function xlib.makelistview( t )
  3511.     local pnl = vgui.Create( "DListView", t.parent )
  3512.     pnl:SetPos( t.x, t.y )
  3513.     pnl:SetSize( t.w, t.h )
  3514.     pnl:SetMultiSelect( t.multiselect )
  3515.     pnl:SetHeaderHeight( t.headerheight or 20 )
  3516.     return pnl
  3517. end
  3518.  
  3519. function xlib.makecat( t )
  3520.     local pnl = vgui.Create( "DCollapsibleCategory", t.parent )
  3521.     pnl:SetPos( t.x, t.y )
  3522.     pnl:SetSize( t.w, t.h )
  3523.     pnl:SetLabel( t.label or "" )
  3524.     pnl:SetContents( t.contents )
  3525.     t.contents:SetParent( pnl )
  3526.     t.contents:Dock( TOP )
  3527.  
  3528.     if t.expanded ~= nil then pnl:SetExpanded( t.expanded ) end
  3529.     if t.checkbox then
  3530.         pnl.checkBox = vgui.Create( "DCheckBox", pnl.Header )
  3531.         pnl.checkBox:SetValue( t.expanded )
  3532.         function pnl.checkBox:DoClick()
  3533.             self:Toggle()
  3534.             pnl:Toggle()
  3535.         end
  3536.         function pnl.Header:OnMousePressed( mcode )
  3537.             if ( mcode == MOUSE_LEFT ) then
  3538.                 self:GetParent():Toggle()
  3539.                 self:GetParent().checkBox:Toggle()
  3540.                 return
  3541.             end
  3542.             return self:GetParent():OnMousePressed( mcode )
  3543.         end
  3544.         local tempfunc = pnl.PerformLayout
  3545.         pnl.PerformLayout = function( self )
  3546.             tempfunc( self )
  3547.             self.checkBox:SetPos( self:GetWide()-18, 2 )
  3548.         end
  3549.     end
  3550.  
  3551.     function pnl:SetOpen( bVal )
  3552.         if not self:GetExpanded() and bVal then
  3553.             pnl.Header:OnMousePressed( MOUSE_LEFT ) --Call the mouse function so it properly toggles the checkbox state (if it exists)
  3554.         elseif self:GetExpanded() and not bVal then
  3555.             pnl.Header:OnMousePressed( MOUSE_LEFT )
  3556.         end
  3557.     end
  3558.  
  3559.     return pnl
  3560. end
  3561.  
  3562. function xlib.makepanel( t )
  3563.     local pnl = vgui.Create( "DPanel", t.parent )
  3564.     pnl:SetPos( t.x, t.y )
  3565.     pnl:SetSize( t.w, t.h )
  3566.     pnl:SetZPos( t.zpos or 0 )
  3567.     if t.skin then pnl:SetSkin( t.skin ) end
  3568.     if t.visible ~= nil then pnl:SetVisible( t.visible ) end
  3569.     return pnl
  3570. end
  3571.  
  3572. function xlib.makeXpanel( t )
  3573.     local pnl = vgui.Create( "xlib_Panel", t.parent )
  3574.     pnl:MakePopup()
  3575.     pnl:SetPos( t.x, t.y )
  3576.     pnl:SetSize( t.w, t.h )
  3577.     if t.visible ~= nil then pnl:SetVisible( t.visible ) end
  3578.     return pnl
  3579. end
  3580.  
  3581. function xlib.makenumberwang( t )
  3582.     local pnl = vgui.Create( "DNumberWang", t.parent )
  3583.     pnl:SetPos( t.x, t.y )
  3584.     pnl:SetDecimals( t.decimal or 0 )
  3585.     pnl:SetMinMax( t.min or 0, t.max or 255 )
  3586.     pnl:SizeToContents()
  3587.     pnl:SetValue( t.value )
  3588.     pnl:SetZPos( t.zpos or 0 )
  3589.     if t.w then pnl:SetWide( t.w ) end
  3590.     if t.h then pnl:SetTall( t.h ) end
  3591.     return pnl
  3592. end
  3593.  
  3594. function xlib.makecombobox( t )
  3595.     local pnl = vgui.Create( "DComboBox", t.parent )
  3596.     t.w = t.w or 100
  3597.     t.h = t.h or 20
  3598.     pnl:SetPos( t.x, t.y )
  3599.     pnl:SetSize( t.w, t.h )
  3600.     pnl:SetZPos( t.zpos or 0 )
  3601.  
  3602.     --Create a textbox to use in place of the button
  3603.     if ( t.enableinput == true ) then
  3604.         pnl.TextEntry = vgui.Create( "DTextEntry", pnl )
  3605.         pnl.TextEntry.selectAll = t.selectall
  3606.         pnl.TextEntry:SetEditable( true )
  3607.  
  3608.         pnl.TextEntry.OnGetFocus = function( self ) --Close the menu when the textbox is clicked, IF the menu was open.
  3609.             hook.Run( "OnTextEntryGetFocus", self )
  3610.             if ( pnl.Menu ) then
  3611.                 pnl.Menu:Remove()
  3612.                 pnl.Menu = nil
  3613.             end
  3614.         end
  3615.  
  3616.         --Override GetValue/SetValue to get/set the text from the TextEntry instead of itself.
  3617.         pnl.GetValue = function( self ) return self.TextEntry:GetValue() end
  3618.         pnl.SetText = function( self, val ) self.TextEntry:SetValue( val ) end
  3619.  
  3620.         pnl.ChooseOption = function( self, value, index ) --Update the text of the TextEntry when an option is selected.
  3621.             if ( self.Menu ) then
  3622.                 self.Menu:Remove()
  3623.                 self.Menu = nil
  3624.             end
  3625.             self.TextEntry:SetText( value )
  3626.             self:OnSelect( index, value, self.Data[index] )
  3627.         end
  3628.  
  3629.         pnl.PerformLayout = function( self ) --Update the size of the textbox when the combobox's PerformLayout is called.
  3630.             self.DropButton:SetSize( 15, 15 )
  3631.             self.DropButton:AlignRight( 4 )
  3632.             self.DropButton:CenterVertical()
  3633.             self.TextEntry:SetSize( self:GetWide()-20, self:GetTall() )
  3634.         end
  3635.     end
  3636.  
  3637.     pnl:SetText( t.text or "" )
  3638.  
  3639.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3640.     if t.tooltip then
  3641.         if t.tooltipwidth ~= 0 then
  3642.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3643.         end
  3644.         pnl:SetTooltip( t.tooltip )
  3645.     end
  3646.  
  3647.     if t.choices then
  3648.         for i, v in ipairs( t.choices ) do
  3649.             pnl:AddChoice( v )
  3650.         end
  3651.     end
  3652.  
  3653.     function pnl:SetDisabled( val ) --enabling/disabling of a textbox
  3654.         self:SetMouseInputEnabled( not val )
  3655.         self:SetAlpha( val and 128 or 255 )
  3656.     end
  3657.     if t.disabled then pnl:SetDisabled( t.disabled ) end
  3658.  
  3659.     --Garrys function with no comments, just adding support for Spacers and setting the skin.
  3660.     function pnl:OpenMenu()
  3661.         if ( #self.Choices == 0 ) then return end
  3662.         if ( IsValid( self.Menu ) ) then
  3663.             self.Menu:Remove()
  3664.             self.Menu = nil
  3665.         end
  3666.         self.Menu = DermaMenu()
  3667.         self.Menu:SetSkin( xgui.settings.skin )
  3668.         for k, v in pairs( self.Choices ) do
  3669.             if v == "--*" then --This is the string to determine where to add the spacer
  3670.                 self.Menu:AddSpacer()
  3671.             else
  3672.                 self.Menu:AddOption( v, function() self:ChooseOption( v, k ) end )
  3673.             end
  3674.         end
  3675.         local x, y = self:LocalToScreen( 0, self:GetTall() )
  3676.         self.Menu:SetMinimumWidth( self:GetWide() )
  3677.         self.Menu:Open( x, y, false, self )
  3678.     end
  3679.  
  3680.     --Replicated Convar Updating
  3681.     if t.repconvar then
  3682.         xlib.checkRepCvarCreated( t.repconvar )
  3683.         if t.isNumberConvar then --This is for convar settings stored via numbers (like ulx_rslotsMode)
  3684.             if t.numOffset == nil then t.numOffset = 1 end
  3685.             local cvar = GetConVar( t.repconvar ):GetInt()
  3686.             if tonumber( cvar ) and cvar + t.numOffset <= #pnl.Choices and cvar + t.numOffset > 0 then
  3687.                 pnl:ChooseOptionID( cvar + t.numOffset )
  3688.             else
  3689.                 pnl:SetText( "Invalid Convar Value" )
  3690.             end
  3691.             function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3692.                 if cl_cvar == t.repconvar:lower() then
  3693.                     if tonumber( new_val ) and new_val + t.numOffset <= #pnl.Choices and new_val + t.numOffset > 0 then
  3694.                         pnl:ChooseOptionID( new_val + t.numOffset )
  3695.                     else
  3696.                         pnl:SetText( "Invalid Convar Value" )
  3697.                     end
  3698.                 end
  3699.             end
  3700.             hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3701.             function pnl:OnSelect( index )
  3702.                 RunConsoleCommand( t.repconvar, tostring( index - t.numOffset ) )
  3703.             end
  3704.         else  --Otherwise, use each choice as a string for the convar
  3705.             pnl:SetText( GetConVar( t.repconvar ):GetString() )
  3706.             function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3707.                 if cl_cvar == t.repconvar:lower() then
  3708.                     if t.convarblanklabel and new_val == "" then new_val = t.convarblanklabel end
  3709.                     pnl:SetText( new_val )
  3710.                 end
  3711.             end
  3712.             hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3713.             function pnl:OnSelect( index, value )
  3714.                 if t.convarblanklabel and value == "<not specified>" then value = "" end
  3715.                 RunConsoleCommand( t.repconvar, value )
  3716.             end
  3717.         end
  3718.     end
  3719.  
  3720.     return pnl
  3721. end
  3722.  
  3723. function xlib.maketree( t )
  3724.     local pnl = vgui.Create( "DTree", t.parent )
  3725.     pnl:SetPos( t.x, t.y )
  3726.     pnl:SetSize( t.w, t.h )
  3727.  
  3728.     function pnl:Clear() --Clears the DTree.
  3729.         if self.RootNode.ChildNodes then
  3730.             for _, node in ipairs( self.RootNode.ChildNodes:GetChildren() ) do
  3731.                 node:Remove()
  3732.             end
  3733.             self.m_pSelectedItem = nil
  3734.             self:InvalidateLayout()
  3735.         end
  3736.     end
  3737.     return pnl
  3738. end
  3739.  
  3740. function xlib.makecolorpicker( t )
  3741.     local pnl = vgui.Create( "xlibColorPanel", t.parent )
  3742.     pnl:SetPos( t.x, t.y )
  3743.     if t.noalphamodetwo then pnl:NoAlphaModeTwo() end --Provide an alternate layout with no alpha bar.
  3744.     if t.addalpha then
  3745.         pnl:AddAlphaBar()
  3746.         if t.alphamodetwo then pnl:AlphaModeTwo() end
  3747.     end
  3748.     if t.color then pnl:SetColor( t.color ) end
  3749.     if t.repconvar then
  3750.         xlib.checkRepCvarCreated( t.repconvar )
  3751.         local col = GetConVar( t.repconvar ):GetString()
  3752.         if col == "0" then col = "0 0 0" end
  3753.         col = string.Split( col, " " )
  3754.         pnl:SetColor( Color( col[1], col[2], col[3] ) )
  3755.         function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3756.             if cl_cvar == t.repconvar:lower() then
  3757.                 local col = string.Split( new_val, " " )
  3758.                 pnl:SetColor( Color( col[1], col[2], col[3] ) )
  3759.             end
  3760.         end
  3761.         hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3762.         function pnl:OnChange( color )
  3763.             RunConsoleCommand( t.repconvar, color.r .. " " .. color.g .. " " .. color.b )
  3764.         end
  3765.     end
  3766.     return pnl
  3767. end
  3768.  
  3769. --Thanks to Megiddo for this code! :D
  3770. function xlib.wordWrap( text, width, font )
  3771.     surface.SetFont( font )
  3772.     if not surface.GetTextSize( "" ) then
  3773.         surface.SetFont( "default" ) --Set font to default if specified font does not return a size properly.
  3774.     end
  3775.     text = text:Trim()
  3776.     local output = ""
  3777.     local pos_start, pos_end = 1, 1
  3778.     while true do
  3779.         local begin, stop = text:find( "%s+", pos_end + 1 )
  3780.  
  3781.         if (surface.GetTextSize( text:sub( pos_start, begin or -1 ):Trim() ) > width and pos_end - pos_start > 0) then -- If it's not going to fit, split into a newline
  3782.             output = output .. text:sub( pos_start, pos_end ):Trim() .. "\n"
  3783.             pos_start = pos_end + 1
  3784.             pos_end = pos_end + 1
  3785.         else
  3786.             pos_end = stop
  3787.         end
  3788.  
  3789.         if not stop then -- We've hit our last word
  3790.             output = output .. text:sub( pos_start ):Trim()
  3791.             break
  3792.         end
  3793.     end
  3794.     return output
  3795. end
  3796.  
  3797. function xlib.makeprogressbar( t )
  3798.     local pnl = vgui.Create( "DProgress", t.parent )
  3799.     pnl.Label = xlib.makelabel{ x=5, y=3, w=(t.w or 100), textcolor=SKIN.text_dark, parent=pnl }
  3800.     pnl:SetPos( t.x, t.y )
  3801.     pnl:SetSize( t.w or 100, t.h or 20 )
  3802.     pnl:SetFraction( t.value or 0 )
  3803.     if t.skin then pnl:SetSkin( t.skin ) end
  3804.     if t.visible ~= nil then pnl:SetVisible( t.visible ) end
  3805.     return pnl
  3806. end
  3807.  
  3808. function xlib.checkRepCvarCreated( cvar )
  3809.     if GetConVar( cvar ) == nil then
  3810.         CreateClientConVar( cvar:lower(), 0, false, false ) --Replicated cvar hasn't been created via ULib. Create a temporary one to prevent errors
  3811.     end
  3812. end
  3813.  
  3814. function xlib.makeslider( t )
  3815.     local pnl = vgui.Create( "DNumSlider", t.parent )
  3816.  
  3817.     pnl.PerformLayout = function() end  -- Clears the code that automatically sets the width of the label to 41% of the entire width.
  3818.  
  3819.     pnl:SetPos( t.x, t.y )
  3820.     pnl:SetWide( t.w or 100 )
  3821.     pnl:SetTall( t.h or 20 )
  3822.     pnl:SetText( t.label or "" )
  3823.     pnl:SetMinMax( t.min or 0, t.max or 100 )
  3824.     pnl:SetDecimals( t.decimal or 0 )
  3825.     pnl.TextArea:SetDrawBackground( true )
  3826.     pnl.TextArea.selectAll = t.selectall
  3827.     pnl.Label:SizeToContents()
  3828.     pnl:SetZPos( t.zpos or 0 )
  3829.  
  3830.     if t.textcolor then
  3831.         pnl.Label:SetTextColor( t.textcolor )
  3832.     else
  3833.         pnl.Label:SetTextColor( SKIN.text_dark )
  3834.     end
  3835.  
  3836.     if t.fixclip then pnl.Slider.Knob:NoClipping( false ) end --Fixes clipping on the knob, an example is the sandbox limit sliders.
  3837.  
  3838.     if t.convar then pnl:SetConVar( t.convar ) end
  3839.     if not t.tooltipwidth then t.tooltipwidth = 250 end
  3840.     if t.tooltip then
  3841.         if t.tooltipwidth ~= 0 then
  3842.             t.tooltip = xlib.wordWrap( t.tooltip, t.tooltipwidth, "Default" )
  3843.         end
  3844.         pnl:SetTooltip( t.tooltip )
  3845.     end
  3846.  
  3847.     --Support for enabling/disabling slider
  3848.     pnl.SetDisabled = function( self, val )
  3849.         pnl:SetAlpha( val and 128 or 255 )
  3850.         pnl:SetEnabled( not val )
  3851.         pnl.TextArea:SetEnabled( not val )
  3852.         pnl.TextArea:SetMouseInputEnabled( not val )
  3853.         pnl.Scratch:SetMouseInputEnabled( not val )
  3854.         pnl.Slider:SetMouseInputEnabled( not val )
  3855.     end
  3856.     if t.disabled then pnl:SetDisabled( t.disabled ) end
  3857.  
  3858.     pnl:SizeToContents()
  3859.  
  3860.     --
  3861.     --The following code bits are basically copies of Garry's code with changes to prevent the slider from sending updates so often
  3862.     pnl.GetValue = function( self ) return tonumber( self.TextArea:GetValue() ) end
  3863.     function pnl.SetValue( self, val )
  3864.         if ( val == nil ) then return end
  3865.         if t.clampmin then val = math.max( tonumber( val ) or 0, self:GetMin() ) end
  3866.         if t.clampmax then val = math.min( tonumber( val ) or 0, self:GetMax() ) end
  3867.         self.Scratch:SetValue( val )
  3868.         self.ValueUpdated( val )
  3869.         self:ValueChanged( val )
  3870.     end
  3871.     function pnl.ValueChanged( self, val )
  3872.         if t.clampmin then val = math.max( tonumber( val ) or 0, self:GetMin() ) end
  3873.         if t.clampmax then val = math.min( tonumber( val ) or 0, self:GetMax() ) end
  3874.         self.Slider:SetSlideX( self.Scratch:GetFraction( val ) )
  3875.         if ( self.TextArea ~= vgui.GetKeyboardFocus() ) then
  3876.             self.TextArea:SetValue( self.Scratch:GetTextValue() )
  3877.         end
  3878.         self:OnValueChanged( val )
  3879.     end
  3880.  
  3881.     --Textbox
  3882.     function pnl.ValueUpdated( value )
  3883.         pnl.TextArea:SetText( string.format("%." .. ( pnl.Scratch:GetDecimals() ) .. "f", tonumber( value ) or 0) )
  3884.     end
  3885.     pnl.TextArea.OnTextChanged = function() end
  3886.     function pnl.TextArea:OnEnter()
  3887.         pnl.TextArea:SetText( string.format("%." .. ( pnl.Scratch:GetDecimals() ) .. "f", tonumber( pnl.TextArea:GetText() ) or 0) )
  3888.         if pnl.OnEnter then pnl:OnEnter() end
  3889.     end
  3890.     function pnl.TextArea:OnLoseFocus()
  3891.         pnl:SetValue( pnl.TextArea:GetText() )
  3892.         hook.Call( "OnTextEntryLoseFocus", nil, self )
  3893.     end
  3894.  
  3895.     --Slider
  3896.     local pnl_val
  3897.     function pnl:TranslateSliderValues( x, y )
  3898.         pnl_val = self.Scratch:GetMin() + (x * self.Scratch:GetRange()) --Store the value and update the textbox to the new value
  3899.         pnl.ValueUpdated( pnl_val )
  3900.         self.Scratch:SetFraction( x )
  3901.  
  3902.         return self.Scratch:GetFraction(), y
  3903.     end
  3904.     local tmpfunc = pnl.Slider.Knob.OnMouseReleased
  3905.     pnl.Slider.Knob.OnMouseReleased = function( self, mcode )
  3906.         tmpfunc( self, mcode )
  3907.         pnl.Slider:OnMouseReleased( mcode )
  3908.     end
  3909.     local tmpfunc = pnl.Slider.SetDragging
  3910.     pnl.Slider.SetDragging = function( self, bval )
  3911.         tmpfunc( self, bval )
  3912.         if ( not bval ) then pnl:SetValue( pnl.TextArea:GetText() ) end
  3913.     end
  3914.     pnl.Slider.OnMouseReleased = function( self, mcode )
  3915.         self:SetDragging( false )
  3916.         self:MouseCapture( false )
  3917.     end
  3918.  
  3919.     --Scratch
  3920.     function pnl.Scratch:OnCursorMoved( x, y )
  3921.         if ( not self:GetActive() ) then return end
  3922.  
  3923.         x = x - math.floor( self:GetWide() * 0.5 )
  3924.         y = y - math.floor( self:GetTall() * 0.5 )
  3925.  
  3926.         local zoom = self:GetZoom()
  3927.         local ControlScale = 100 / zoom;
  3928.         local maxzoom = 20
  3929.         if ( self:GetDecimals() ) then
  3930.             maxzoom = 10000
  3931.         end
  3932.         zoom = math.Clamp( zoom + ((y * -0.6) / ControlScale), 0.01, maxzoom );
  3933.         self:SetZoom( zoom )
  3934.  
  3935.         local value = self:GetFloatValue()
  3936.         value = math.Clamp( value + (x * ControlScale * 0.002), self:GetMin(), self:GetMax() );
  3937.         self:SetFloatValue( value )
  3938.         pnl_val = value --Store value for later
  3939.         pnl.ValueUpdated( pnl_val )
  3940.  
  3941.         self:LockCursor()
  3942.     end
  3943.     pnl.Scratch.OnMouseReleased = function( self, mousecode )
  3944.         g_Active = nil
  3945.  
  3946.         self:SetActive( false )
  3947.         self:MouseCapture( false )
  3948.         self:SetCursor( "sizewe" )
  3949.  
  3950.         pnl:SetValue( pnl.TextArea:GetText() )
  3951.     end
  3952.     --End code changes
  3953.     --
  3954.  
  3955.     if t.value then pnl:SetValue( t.value ) end
  3956.  
  3957.     --Replicated Convar Updating
  3958.     if t.repconvar then
  3959.         xlib.checkRepCvarCreated( t.repconvar )
  3960.         pnl:SetValue( GetConVar( t.repconvar ):GetFloat() )
  3961.         function pnl.ConVarUpdated( sv_cvar, cl_cvar, ply, old_val, new_val )
  3962.             if cl_cvar == t.repconvar:lower() then
  3963.                 if ( IsValid( pnl ) ) then  --Prevents random errors when joining.
  3964.                     pnl:SetValue( new_val )
  3965.                 end
  3966.             end
  3967.         end
  3968.         hook.Add( "ULibReplicatedCvarChanged", "XLIB_" .. t.repconvar, pnl.ConVarUpdated )
  3969.         function pnl:OnValueChanged( val )
  3970.             RunConsoleCommand( t.repconvar, tostring( val ) )
  3971.         end
  3972.         --Override think functions to remove Garry's convar check to (hopefully) speed things up
  3973.         pnl.ConVarNumberThink = function() end
  3974.         pnl.ConVarStringThink = function() end
  3975.         pnl.ConVarChanged = function() end
  3976.     end
  3977.  
  3978.     return pnl
  3979. end
  3980.  
  3981. -----------------------------------------
  3982. --A stripped-down customized DPanel allowing for textbox input!
  3983. -----------------------------------------
  3984. local PANEL = {}
  3985. AccessorFunc( PANEL, "m_bPaintBackground", "PaintBackground" )
  3986. Derma_Hook( PANEL, "Paint", "Paint", "Panel" )
  3987. Derma_Hook( PANEL, "ApplySchemeSettings", "Scheme", "Panel" )
  3988.  
  3989. function PANEL:Init()
  3990.     self:SetPaintBackground( true )
  3991. end
  3992.  
  3993. derma.DefineControl( "xlib_Panel", "", PANEL, "EditablePanel" )
  3994.  
  3995.  
  3996. -----------------------------------------
  3997. --A copy of Garry's ColorCtrl used in the sandbox spawnmenu, with the following changes:
  3998. -- -Doesn't use convars whatsoever
  3999. -- -Is a fixed size, but you can have it with/without the alphabar, and there's two layout styles without the alpha bar.
  4000. -- -Has two functions: OnChange and OnChangeImmediate for greater control of handling changes.
  4001. -----------------------------------------
  4002. local PANEL = {}
  4003. function PANEL:Init()
  4004.     self.showAlpha=false
  4005.  
  4006.     self:SetSize( 130, 135 )
  4007.  
  4008.     self.RGBBar = vgui.Create( "DRGBPicker", self )
  4009.     self.RGBBar.OnChange = function( ctrl, color )
  4010.         if ( self.showAlpha ) then
  4011.             color.a = self.txtA:GetValue()
  4012.         end
  4013.         self:SetBaseColor( color )
  4014.     end
  4015.     self.RGBBar:SetSize( 15, 100 )
  4016.     self.RGBBar:SetPos( 5,5 )
  4017.     self.RGBBar.OnMouseReleased = function( self, mcode )
  4018.         self:MouseCapture( false )
  4019.         self:OnCursorMoved( self:CursorPos() )
  4020.         self:GetParent():OnChange( self:GetParent():GetColor() )
  4021.     end
  4022.     function self.RGBBar:SetColor( color )
  4023.         local h, s, v = ColorToHSV( color )
  4024.         self.LastY = ( 1 - h / 360 ) * self:GetTall()
  4025.     end
  4026.  
  4027.     self.ColorCube = vgui.Create( "DColorCube", self )
  4028.     self.ColorCube.OnUserChanged = function( ctrl ) self:ColorCubeChanged( ctrl ) end
  4029.     self.ColorCube:SetSize( 100, 100 )
  4030.     self.ColorCube:SetPos( 25,5 )
  4031.     self.ColorCube.OnMouseReleased = function( self, mcode )
  4032.         self:SetDragging( false )
  4033.         self:MouseCapture( false )
  4034.         self:GetParent():OnChange( self:GetParent():GetColor() )
  4035.     end
  4036.     self.ColorCube.Knob.OnMouseReleased = function( self, mcode )
  4037.         self:GetParent():GetParent():OnChange( self:GetParent():GetParent():GetColor() )
  4038.         return DLabel.OnMouseReleased( self, mousecode )
  4039.     end
  4040.  
  4041.     self.txtR = xlib.makenumberwang{ x=7, y=110, w=35, value=255, parent=self }
  4042.     self.txtR.OnValueChanged = function( self, val )
  4043.         local p = self:GetParent()
  4044.         p:SetColor( Color( val, p.txtG:GetValue(), p.txtB:GetValue(), p.showAlpha and p.txtA:GetValue() ) )
  4045.     end
  4046.     self.txtR.OnEnter = function( self )
  4047.         local val = tonumber( self:GetValue() )
  4048.         if not val then val = 0 end
  4049.         self:OnValueChanged( val )
  4050.     end
  4051.     self.txtR.OnTextChanged = function( self )
  4052.         local val = tonumber( self:GetValue() )
  4053.         if not val then val = 0 end
  4054.         if val ~= math.Clamp( val, 0, 255 ) then self:SetValue( math.Clamp( val, 0, 255 ) ) end
  4055.         self:GetParent():UpdateColorText()
  4056.     end
  4057.     self.txtR.OnLoseFocus = function( self )
  4058.         if not tonumber( self:GetValue() ) then self:SetValue( "0" ) end
  4059.         local p = self:GetParent()
  4060.         p:OnChange( p:GetColor() )
  4061.         hook.Call( "OnTextEntryLoseFocus", nil, self )
  4062.     end
  4063.     self.txtR.Up.DoClick = function( button, mcode )
  4064.         self.txtR:SetValue( tonumber( self.txtR:GetValue() ) + 1 )
  4065.         self.txtR:GetParent():OnChange( self.txtR:GetParent():GetColor() )
  4066.     end
  4067.     self.txtR.Down.DoClick = function( button, mcode )
  4068.         self.txtR:SetValue( tonumber( self.txtR:GetValue() ) - 1 )
  4069.         self.txtR:GetParent():OnChange( self.txtR:GetParent():GetColor() )
  4070.     end
  4071.     function self.txtR.OnMouseReleased( self, mousecode )
  4072.         if ( self.Dragging ) then
  4073.             self:GetParent():OnChange( self:GetParent():GetColor() )
  4074.             self:EndWang()
  4075.         return end
  4076.     end
  4077.     self.txtG = xlib.makenumberwang{ x=47, y=110, w=35, value=100, parent=self }
  4078.     self.txtG.OnValueChanged = function( self, val )
  4079.         local p = self:GetParent()
  4080.         p:SetColor( Color( p.txtR:GetValue(), val, p.txtB:GetValue(), p.showAlpha and p.txtA:GetValue() ) )
  4081.     end
  4082.     self.txtG.OnEnter = function( self )
  4083.         local val = tonumber( self:GetValue() )
  4084.         if not val then val = 0 end
  4085.         self:OnValueChanged( val )
  4086.     end
  4087.     self.txtG.OnTextChanged = function( self )
  4088.         local val = tonumber( self:GetValue() )
  4089.         if not val then val = 0 end
  4090.         if val ~= math.Clamp( val, 0, 255 ) then self:SetValue( math.Clamp( val, 0, 255 ) ) end
  4091.         self:GetParent():UpdateColorText()
  4092.     end
  4093.     self.txtG.OnLoseFocus = function( self )
  4094.         if not tonumber( self:GetValue() ) then self:SetValue( "0" ) end
  4095.         local p = self:GetParent()
  4096.         p:OnChange( p:GetColor() )
  4097.         hook.Call( "OnTextEntryLoseFocus", nil, self )
  4098.     end
  4099.     self.txtG.Up.DoClick = function( button, mcode )
  4100.         self.txtG:SetValue( tonumber( self.txtG:GetValue() ) + 1 )
  4101.         self.txtG:GetParent():OnChange( self.txtG:GetParent():GetColor() )
  4102.     end
  4103.     self.txtG.Down.DoClick = function( button, mcode )
  4104.         self.txtG:SetValue( tonumber( self.txtG:GetValue() ) - 1 )
  4105.         self.txtG:GetParent():OnChange( self.txtG:GetParent():GetColor() )
  4106.     end
  4107.     function self.txtG.OnMouseReleased( self, mousecode )
  4108.         if ( self.Dragging ) then
  4109.             self:GetParent():OnChange( self:GetParent():GetColor() )
  4110.             self:EndWang()
  4111.         return end
  4112.     end
  4113.     self.txtB = xlib.makenumberwang{ x=87, y=110, w=35, value=100, parent=self }
  4114.     self.txtB.OnValueChanged = function( self, val )
  4115.         local p = self:GetParent()
  4116.         p:SetColor( Color( p.txtR:GetValue(), p.txtG:GetValue(), val, p.showAlpha and p.txtA:GetValue() ) )
  4117.     end
  4118.     self.txtB.OnEnter = function( self )
  4119.         local val = tonumber( self:GetValue() )
  4120.         if not val then val = 0 end
  4121.         self:OnValueChanged( val )
  4122.     end
  4123.     self.txtB.OnTextChanged = function( self )
  4124.         local val = tonumber( self:GetValue() )
  4125.         if not val then val = 0 end
  4126.         if val ~= math.Clamp( val, 0, 255 ) then self:SetValue( math.Clamp( val, 0, 255 ) ) end
  4127.         self:GetParent():UpdateColorText()
  4128.     end
  4129.     self.txtB.OnLoseFocus = function( self )
  4130.         if not tonumber( self:GetValue() ) then self:SetValue( "0" ) end
  4131.         local p = self:GetParent()
  4132.         p:OnChange( p:GetColor() )
  4133.         hook.Call( "OnTextEntryLoseFocus", nil, self )
  4134.     end
  4135.     self.txtB.Up.DoClick = function( button, mcode )
  4136.         self.txtB:SetValue( tonumber( self.txtB:GetValue() ) + 1 )
  4137.         self.txtB:GetParent():OnChange( self.txtB:GetParent():GetColor() )
  4138.     end
  4139.     self.txtB.Down.DoClick = function( button, mcode )
  4140.         self.txtB:SetValue( tonumber( self.txtB:GetValue() ) - 1 )
  4141.         self.txtB:GetParent():OnChange( self.txtB:GetParent():GetColor() )
  4142.     end
  4143.     function self.txtB.OnMouseReleased( self, mousecode )
  4144.         if ( self.Dragging ) then
  4145.             self:GetParent():OnChange( self:GetParent():GetColor() )
  4146.             self:EndWang()
  4147.         return end
  4148.     end
  4149.  
  4150.     self:SetColor( Color( 255, 0, 0, 255 ) )
  4151. end
  4152.  
  4153. function PANEL:AddAlphaBar()
  4154.     self.showAlpha = true
  4155.     self.txtA = xlib.makenumberwang{ x=150, y=82, w=35, value=255, parent=self }
  4156.     self.txtA.OnValueChanged = function( self, val )
  4157.         local p = self:GetParent()
  4158.         p:SetColor( Color( p.txtR:GetValue(), p.txtG:GetValue(), p.txtB:GetValue(), val ) )
  4159.     end
  4160.     self.txtA.OnEnter = function( self )
  4161.         local val = tonumber( self:GetValue() )
  4162.         if not val then val = 0 end
  4163.         self:OnValueChanged( val )
  4164.     end
  4165.     self.txtA.OnTextChanged = function( self )
  4166.         local p = self:GetParent()
  4167.         local val = tonumber( self:GetValue() )
  4168.         if not val then val = 0 end
  4169.         if val ~= math.Clamp( val, 0, 255 ) then self:SetValue( math.Clamp( val, 0, 255 ) ) end
  4170.         p.AlphaBar:SetValue( 1 - ( val / 255) )
  4171.         p:OnChangeImmediate( p:GetColor() )
  4172.     end
  4173.     self.txtA.OnLoseFocus = function( self )
  4174.         if not tonumber( self:GetValue() ) then self:SetValue( "0" ) end
  4175.         local p = self:GetParent()
  4176.         p:OnChange( p:GetColor() )
  4177.         hook.Call( "OnTextEntryLoseFocus", nil, self )
  4178.     end
  4179.     self.txtA.Up.DoClick = function( button, mcode )
  4180.         self.txtA:SetValue( tonumber( self.txtA:GetValue() ) + 1 )
  4181.         self.txtA:GetParent():OnChange( self.txtA:GetParent():GetColor() )
  4182.     end
  4183.     self.txtA.Down.DoClick = function( button, mcode )
  4184.         self.txtA:SetValue( tonumber( self.txtA:GetValue() ) - 1 )
  4185.         self.txtA:GetParent():OnChange( self.txtA:GetParent():GetColor() )
  4186.     end
  4187.     function self.txtA.OnMouseReleased( self, mousecode )
  4188.         if ( self.Dragging ) then
  4189.             self:GetParent():OnChange( self:GetParent():GetColor() )
  4190.             self:EndWang()
  4191.         return end
  4192.     end
  4193.  
  4194.     self.AlphaBar = vgui.Create( "DAlphaBar", self )
  4195.     self.AlphaBar.OnChange = function( ctrl, alpha ) self:SetColorAlpha( alpha*255 ) end
  4196.     self.AlphaBar:SetPos( 25,5 )
  4197.     self.AlphaBar:SetSize( 15, 100 )
  4198.     self.AlphaBar:SetValue( 1 )
  4199.     self.AlphaBar.OnMouseReleased = function( self, mcode )
  4200.         self:MouseCapture( false )
  4201.         self:OnCursorMoved( self:CursorPos() )
  4202.         self:GetParent():OnChange( self:GetParent():GetColor() )
  4203.     end
  4204.  
  4205.     self.ColorCube:SetPos( 45,5 )
  4206.     self:SetSize( 190, 110 )
  4207.     self.txtR:SetPos( 150, 7 )
  4208.     self.txtG:SetPos( 150, 32 )
  4209.     self.txtB:SetPos( 150, 57 )
  4210. end
  4211.  
  4212. function PANEL:AlphaModeTwo()
  4213.     self:SetSize( 156, 135 )
  4214.     self.AlphaBar:SetPos( 28,5 )
  4215.     self.ColorCube:SetPos( 51,5 )
  4216.     self.txtR:SetPos( 5, 110 )
  4217.     self.txtG:SetPos( 42, 110 )
  4218.     self.txtB:SetPos( 79, 110 )
  4219.     self.txtA:SetPos( 116, 110 )
  4220. end
  4221.  
  4222. function PANEL:NoAlphaModeTwo()
  4223.     self:SetSize( 170, 110 )
  4224.     self.txtR:SetPos( 130, 7 )
  4225.     self.txtG:SetPos( 130, 32 )
  4226.     self.txtB:SetPos( 130, 57 )
  4227. end
  4228.  
  4229. function PANEL:UpdateColorText()
  4230.     self.RGBBar:SetColor( Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue(), self.showAlpha and self.txtA:GetValue() ) )
  4231.     self.ColorCube:SetColor( Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue(), self.showAlpha and self.txtA:GetValue() ) )
  4232.     if ( self.showAlpha ) then self.AlphaBar:SetBarColor( Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue(), 255 ) ) end
  4233.     self:OnChangeImmediate( self:GetColor() )
  4234. end
  4235.  
  4236. function PANEL:SetColor( color )
  4237.     self.RGBBar:SetColor( color )
  4238.     self.ColorCube:SetColor( color )
  4239.  
  4240.     if tonumber( self.txtR:GetValue() ) ~= color.r then self.txtR:SetText( color.r or 255 ) end
  4241.     if tonumber( self.txtG:GetValue() ) ~= color.g then self.txtG:SetText( color.g or 0 ) end
  4242.     if tonumber( self.txtB:GetValue() ) ~= color.b then self.txtB:SetText( color.b or 0 ) end
  4243.  
  4244.     if ( self.showAlpha ) then
  4245.         self.txtA:SetText( color.a or 0 )
  4246.         self.AlphaBar:SetBarColor( Color( color.r, color.g, color.b ) )
  4247.         self.AlphaBar:SetValue( ( ( color.a or 0 ) / 255) )
  4248.     end
  4249.  
  4250.     self:OnChangeImmediate( color )
  4251. end
  4252.  
  4253. function PANEL:SetBaseColor( color )
  4254.     self.ColorCube:SetBaseRGB( color )
  4255.  
  4256.     self.txtR:SetText(self.ColorCube.m_OutRGB.r)
  4257.     self.txtG:SetText(self.ColorCube.m_OutRGB.g)
  4258.     self.txtB:SetText(self.ColorCube.m_OutRGB.b)
  4259.  
  4260.     if ( self.showAlpha ) then
  4261.         self.AlphaBar:SetBarColor( Color( self:GetColor().r, self:GetColor().g, self:GetColor().b ) )
  4262.     end
  4263.     self:OnChangeImmediate( self:GetColor() )
  4264. end
  4265.  
  4266. function PANEL:SetColorAlpha( alpha )
  4267.     if ( self.showAlpha ) then
  4268.         alpha = alpha or 0
  4269.         self.txtA:SetValue(alpha)
  4270.     end
  4271. end
  4272.  
  4273. function PANEL:ColorCubeChanged( cube )
  4274.     self.txtR:SetText(cube.m_OutRGB.r)
  4275.     self.txtG:SetText(cube.m_OutRGB.g)
  4276.     self.txtB:SetText(cube.m_OutRGB.b)
  4277.     if ( self.showAlpha ) then
  4278.         self.AlphaBar:SetBarColor( Color( self:GetColor().r, self:GetColor().g, self:GetColor().b ) )
  4279.     end
  4280.     self:OnChangeImmediate( self:GetColor() )
  4281. end
  4282.  
  4283. function PANEL:GetColor()
  4284.     local color = Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue() )
  4285.     if ( self.showAlpha ) then
  4286.         color.a = self.txtA:GetValue()
  4287.     else
  4288.         color.a = 255
  4289.     end
  4290.     return color
  4291. end
  4292.  
  4293. function PANEL:PerformLayout()
  4294.     self:SetColor( Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue(), self.showAlpha and self.txtA:GetValue() ) )
  4295. end
  4296.  
  4297. function PANEL:OnChangeImmediate( color )
  4298.     --For override
  4299. end
  4300.  
  4301. function PANEL:OnChange( color )
  4302.     --For override
  4303. end
  4304.  
  4305. vgui.Register( "xlibColorPanel", PANEL, "DPanel" )
  4306.  
  4307.  
  4308. -- Create font for Ban Message preview to match the font used in the actual banned/disconnect dialog.
  4309. surface.CreateFont ("DefaultLarge", {
  4310.     font = "Tahoma",
  4311.     size = 16,
  4312.     weight = 0,
  4313. })
  4314.  
  4315. -------------------------
  4316. --Custom Animation System
  4317. -------------------------
  4318. --This is a heavily edited version of Garry's derma animation stuff with the following differences:
  4319.     --Allows for animation chains (one animation to begin right after the other)
  4320.     --Can call functions anywhere during the animation cycle.
  4321.     --Reliably calls a start/end function for each animation so the animations always shows/ends properly.
  4322.     --Animations can be completely disabled by setting 0 for the animation time.
  4323. local xlibAnimation = {}
  4324. xlibAnimation.__index = xlibAnimation
  4325.  
  4326. function xlib.anim( runFunc, startFunc, endFunc )
  4327.     local anim = {}
  4328.     anim.runFunc = runFunc
  4329.     anim.startFunc = startFunc
  4330.     anim.endFunc = endFunc
  4331.     setmetatable( anim, xlibAnimation )
  4332.     return anim
  4333. end
  4334.  
  4335. xlib.animTypes = {}
  4336. xlib.registerAnimType = function( name, runFunc, startFunc, endFunc )
  4337.     xlib.animTypes[name] = xlib.anim( runFunc, startFunc, endFunc )
  4338. end
  4339.  
  4340. function xlibAnimation:Start( Length, Data )
  4341.     self.startFunc( Data )
  4342.     if ( Length == 0 ) then
  4343.         self.endFunc( Data )
  4344.         xlib.animQueue_call()
  4345.     else
  4346.         self.Length = Length
  4347.         self.StartTime = SysTime()
  4348.         self.EndTime = SysTime() + Length
  4349.         self.Data = Data
  4350.         table.insert( xlib.activeAnims, self )
  4351.     end
  4352. end
  4353.  
  4354. function xlibAnimation:Stop()
  4355.     self.runFunc( 1, self.Data )
  4356.     self.endFunc( self.Data )
  4357.     for i, v in ipairs( xlib.activeAnims ) do
  4358.         if v == self then table.remove( xlib.activeAnims, i ) break end
  4359.     end
  4360.     xlib.animQueue_call()
  4361. end
  4362.  
  4363. function xlibAnimation:Run()
  4364.     local CurTime = SysTime()
  4365.     local delta = (CurTime - self.StartTime) / self.Length
  4366.     if ( CurTime > self.EndTime ) then
  4367.         self:Stop()
  4368.     else
  4369.         self.runFunc( delta, self.Data )
  4370.     end
  4371. end
  4372.  
  4373. --Animation Ticker
  4374. xlib.activeAnims = {}
  4375. xlib.animRun = function()
  4376.     for _, v in ipairs( xlib.activeAnims ) do
  4377.         v.Run( v )
  4378.     end
  4379. end
  4380. hook.Add( "XLIBDoAnimation", "xlib_runAnims", xlib.animRun )
  4381.  
  4382. -------------------------
  4383. --Animation chain manager
  4384. -------------------------
  4385. xlib.animQueue = {}
  4386. xlib.animBackupQueue = {}
  4387.  
  4388. --This will allow us to make animations run faster when linked together
  4389. --Makes sure the entire animation length = animationTime (~0.2 sec by default)
  4390. xlib.animStep = 0
  4391.  
  4392. --Call this to begin the animation chain
  4393. xlib.animQueue_start = function()
  4394.     if xlib.animRunning then --If a new animation is starting while one is running, then we should instantly stop the old one.
  4395.         xlib.animQueue_forceStop()
  4396.         return --The old animation should be finished now, and the new one should be starting
  4397.     end
  4398.     xlib.curAnimStep = xlib.animStep
  4399.     xlib.animStep = 0
  4400.     xlib.animQueue_call()
  4401. end
  4402.  
  4403. xlib.animQueue_forceStop = function()
  4404.     --This will trigger the currently chained animations to run at 0 seconds.
  4405.     xlib.curAnimStep = -1
  4406.     if type( xlib.animRunning ) == "table" then xlib.animRunning:Stop() end
  4407. end
  4408.  
  4409. xlib.animQueue_call = function()
  4410.     if #xlib.animQueue > 0 then
  4411.         local func = xlib.animQueue[1]
  4412.         table.remove( xlib.animQueue, 1 )
  4413.         func()
  4414.     else
  4415.         xlib.animRunning = nil
  4416.         --Check for queues in the backup that haven't been started.
  4417.         if #xlib.animBackupQueue > 0 then
  4418.             xlib.animQueue = table.Copy( xlib.animBackupQueue )
  4419.             xlib.animBackupQueue = {}
  4420.             xlib.animQueue_start()
  4421.         end
  4422.     end
  4423. end
  4424.  
  4425. xlib.addToAnimQueue = function( obj, ... )
  4426.     local arg = { ... }
  4427.     --If there is an animation running, then we need to store the new animation stuff somewhere else temporarily.
  4428.     --Also, if ignoreRunning is true, then we'll add the anim to the regular queue regardless of running status.
  4429.     local outTable = xlib.animRunning and xlib.animBackupQueue or xlib.animQueue
  4430.  
  4431.     if type( obj ) == "function" then
  4432.         table.insert( outTable, function() xlib.animRunning = true  obj( unpack( arg ) )  xlib.animQueue_call() end )
  4433.     elseif type( obj ) == "string" and xlib.animTypes[obj] then
  4434.         --arg[1] should be data table, arg[2] should be length
  4435.         length = arg[2] or xgui.settings.animTime or 1
  4436.         xlib.animStep = xlib.animStep + 1
  4437.         table.insert( outTable, function() xlib.animRunning = xlib.animTypes[obj]  xlib.animRunning:Start( ( xlib.curAnimStep ~= -1 and ( length/xlib.curAnimStep ) or 0 ), arg[1] ) end )
  4438.     else
  4439.         Msg( "Error: XLIB recieved an invalid animation call! TYPE:" .. type( obj ) .. " VALUE:" .. tostring( obj ) .. "\n" )
  4440.     end
  4441. end
  4442.  
  4443. -------------------------
  4444. --Default Animation Types
  4445. -------------------------
  4446. --Slide animation
  4447. local function slideAnim_run( delta, data )
  4448.     --data.panel, data.startx, data.starty, data.endx, data.endy, data.setvisible
  4449.     data.panel:SetPos( data.startx+((data.endx-data.startx)*delta), data.starty+((data.endy-data.starty)*delta) )
  4450. end
  4451.  
  4452. local function slideAnim_start( data )
  4453.     data.panel:SetPos( data.startx, data.starty )
  4454.     if data.setvisible == true then
  4455.         ULib.queueFunctionCall( data.panel.SetVisible, data.panel, true )
  4456.     end
  4457. end
  4458.  
  4459. local function slideAnim_end( data )
  4460.     data.panel:SetPos( data.endx, data.endy )
  4461.     if data.setvisible == false then
  4462.         data.panel:SetVisible( false )
  4463.     end
  4464. end
  4465. xlib.registerAnimType( "pnlSlide", slideAnim_run, slideAnim_start, slideAnim_end )
  4466.  
  4467. --Fade animation
  4468. local function fadeAnim_run( delta, data )
  4469.     if data.panelOut then data.panelOut:SetAlpha( 255-(delta*255) ) data.panelOut:SetVisible( true ) end
  4470.     if data.panelIn then data.panelIn:SetAlpha( 255 * delta ) data.panelIn:SetVisible( true ) end
  4471. end
  4472.  
  4473. local function fadeAnim_start( data )
  4474.     if data.panelOut then data.panelOut:SetAlpha( 255 ) data.panelOut:SetVisible( true ) end
  4475.     if data.panelIn then data.panelIn:SetAlpha( 0 ) data.panelIn:SetVisible( true ) end
  4476. end
  4477.  
  4478. local function fadeAnim_end( data )
  4479.     if data.panelOut then data.panelOut:SetVisible( false ) end
  4480.     if data.panelIn then data.panelIn:SetAlpha( 255 ) end
  4481. end
  4482. xlib.registerAnimType( "pnlFade", fadeAnim_run, fadeAnim_start, fadeAnim_end )
  4483.  
  4484. --xgui_helpers -- by Stickly Man!
  4485. --A set of generic functions to help with various XGUI-related things.
  4486.  
  4487. function xgui.load_helpers()
  4488.     --These handle keyboard focus for textboxes within XGUI.
  4489.     local function getKeyboardFocus( pnl )
  4490.         if pnl:HasParent( xgui.base ) then
  4491.             xgui.anchor:SetKeyboardInputEnabled( true )
  4492.         end
  4493.         if pnl.selectAll then
  4494.             pnl:SelectAllText()
  4495.         end
  4496.     end
  4497.     hook.Add( "OnTextEntryGetFocus", "XGUI_GetKeyboardFocus", getKeyboardFocus )
  4498.  
  4499.     local function loseKeyboardFocus( pnl )
  4500.         if pnl:HasParent( xgui.base ) then
  4501.             xgui.anchor:SetKeyboardInputEnabled( false )
  4502.         end
  4503.     end
  4504.     hook.Add( "OnTextEntryLoseFocus", "XGUI_LoseKeyboardFocus", loseKeyboardFocus )
  4505.  
  4506.  
  4507.     ---------------------------------
  4508.     --Code for creating the XGUI base
  4509.     ---------------------------------
  4510.     function xgui.makeXGUIbase()
  4511.         xgui.anchor = xlib.makeXpanel{ w=600, h=420, x=ScrW()/2-300, y=ScrH()/2-270 }
  4512.         xgui.anchor:SetVisible( false )
  4513.         xgui.anchor:SetKeyboardInputEnabled( false )
  4514.         xgui.anchor.Paint = function( self, w, h ) hook.Call( "XLIBDoAnimation" ) end
  4515.         xgui.anchor:SetAlpha( 0 )
  4516.  
  4517.         xgui.base = xlib.makepropertysheet{ x=0, y=0, w=600, h=400, parent=xgui.anchor, offloadparent=xgui.null }
  4518.         xgui.base.animOpen = function() --First 4 are fade animations, last (or invalid choice) is the default fade animation.
  4519.             xgui.settings.animIntype = tonumber( xgui.settings.animIntype )
  4520.             if xgui.settings.animIntype == 2 then
  4521.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(255) end )
  4522.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=-490, endx=xgui.x, endy=xgui.y, setvisible=true } )
  4523.             elseif xgui.settings.animIntype == 3 then
  4524.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(255) end )
  4525.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=-610, starty=xgui.y, endx=xgui.x, endy=xgui.y, setvisible=true } )
  4526.             elseif xgui.settings.animIntype == 4 then
  4527.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(255) end )
  4528.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=ScrH(), endx=xgui.x, endy=xgui.y, setvisible=true } )
  4529.             elseif xgui.settings.animIntype == 5 then
  4530.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(255) end )
  4531.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=ScrW(), starty=xgui.y, endx=xgui.x, endy=xgui.y, setvisible=true } )
  4532.             else
  4533.                 xlib.addToAnimQueue( function() xgui.anchor:SetPos( xgui.x, xgui.y ) end )
  4534.                 xlib.addToAnimQueue( "pnlFade", { panelIn=xgui.anchor } )
  4535.             end
  4536.             xlib.animQueue_start()
  4537.         end
  4538.         xgui.base.animClose = function()
  4539.             xgui.settings.animOuttype = tonumber( xgui.settings.animOuttype )
  4540.             if xgui.settings.animOuttype == 2 then
  4541.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=xgui.y, endx=xgui.x, endy=-490, setvisible=false } )
  4542.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(0) end )
  4543.             elseif xgui.settings.animOuttype == 3 then
  4544.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=xgui.y, endx=-610, endy=xgui.y, setvisible=false } )
  4545.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(0) end )
  4546.             elseif xgui.settings.animOuttype == 4 then
  4547.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=xgui.y, endx=xgui.x, endy=ScrH(), setvisible=false } )
  4548.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(0) end )
  4549.             elseif xgui.settings.animOuttype == 5 then
  4550.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=xgui.x, starty=xgui.y, endx=ScrW(), endy=xgui.y, setvisible=false } )
  4551.                 xlib.addToAnimQueue( function() xgui.anchor:SetAlpha(0) end )
  4552.             else
  4553.                 xlib.addToAnimQueue( function() xgui.anchor:SetPos( xgui.x, xgui.y ) end )
  4554.                 xlib.addToAnimQueue( "pnlFade", { panelOut=xgui.anchor } )
  4555.             end
  4556.             xlib.animQueue_start()
  4557.         end
  4558.  
  4559.         function xgui.SetPos( pos, xoff, yoff, ignoreanim ) --Sets the position of XGUI based on "pos", and checks to make sure that with whatever offset and pos combination, XGUI does not go off the screen.
  4560.             pos = tonumber( pos )
  4561.             xoff = tonumber( xoff )
  4562.             yoff = tonumber( yoff )
  4563.             if not xoff then xoff = 0 end
  4564.             if not yoff then yoff = 0 end
  4565.             if not pos then pos = 5 end
  4566.             if pos == 1 or pos == 4 or pos == 7 then --Left side of the screen
  4567.                 if xoff < -10 then
  4568.                     xoff = -10
  4569.                 elseif xoff > ScrW()-610 then
  4570.                     xoff = ScrW()-610
  4571.                 end
  4572.                 xgui.x = 10+xoff
  4573.             elseif pos == 3 or pos == 6 or pos == 9 then --Right side of the screen
  4574.                 if xoff < -ScrW()+610 then
  4575.                     xoff = -ScrW()+610
  4576.                 elseif xoff > 10 then
  4577.                     xoff = 10
  4578.                 end
  4579.                 xgui.x = ScrW()-610+xoff
  4580.             else --Center
  4581.                 if xoff < -ScrW()/2+300 then
  4582.                     xoff = -ScrW()/2+300
  4583.                 elseif xoff > ScrW()/2-300 then
  4584.                     xoff = ScrW()/2-300
  4585.                 end
  4586.                 xgui.x = ScrW()/2-300+xoff
  4587.             end
  4588.  
  4589.             if pos == 1 or pos == 2 or pos == 3 then --Bottom of the screen
  4590.                 if yoff < -ScrH()+430 then
  4591.                     yoff = -ScrH()+430
  4592.                 elseif yoff > 30 then
  4593.                     yoff = 30
  4594.                 end
  4595.                 xgui.y = ScrH()-430+yoff
  4596.             elseif pos == 7 or pos == 8 or pos == 9 then --Top of the screen
  4597.                 if yoff < -10 then
  4598.                     yoff = -10
  4599.                 elseif yoff > ScrH()-410 then
  4600.                     yoff = ScrH()-410
  4601.                 end
  4602.                 xgui.y = yoff+10
  4603.             else --Center
  4604.                 if yoff < -ScrH()/2+210 then
  4605.                     yoff = -ScrH()/2+210
  4606.                 elseif yoff > ScrH()/2-190 then
  4607.                     yoff = ScrH()/2-190
  4608.                 end
  4609.                 xgui.y = ScrH()/2-210+yoff
  4610.             end
  4611.             if ignoreanim then
  4612.                 xgui.anchor:SetPos( xgui.x, xgui.y )
  4613.             else
  4614.                 local curx, cury = xgui.anchor:GetPos()
  4615.                 xlib.addToAnimQueue( "pnlSlide", { panel=xgui.anchor, startx=curx, starty=cury, endx=xgui.x, endy=xgui.y } )
  4616.                 xlib.animQueue_start()
  4617.             end
  4618.         end
  4619.         xgui.SetPos( xgui.settings.xguipos.pos, xgui.settings.xguipos.xoff, xgui.settings.xguipos.yoff )
  4620.  
  4621.         function xgui.base:SetActiveTab( active, ignoreAnim )
  4622.             if ( self.m_pActiveTab == active ) then return end
  4623.             if ( self.m_pActiveTab ) then
  4624.                 if not ignoreAnim then
  4625.                     xlib.addToAnimQueue( "pnlFade", { panelOut=self.m_pActiveTab:GetPanel(), panelIn=active:GetPanel() } )
  4626.                 else
  4627.                     --Run this when module permissions have changed.
  4628.                     xlib.addToAnimQueue( "pnlFade", { panelOut=nil, panelIn=active:GetPanel() }, 0 )
  4629.                 end
  4630.                 xlib.animQueue_start()
  4631.             end
  4632.             self.m_pActiveTab = active
  4633.             self:InvalidateLayout()
  4634.         end
  4635.  
  4636.         --Progress bar
  4637.         xgui.chunkbox = xlib.makeprogressbar{ x=420, w=180, h=20, visible=false, skin=xgui.settings.skin, parent=xgui.anchor }
  4638.         function xgui.chunkbox:Progress( datatype )
  4639.             self.value = self.value + 1
  4640.             self:SetFraction( self.value / self.max )
  4641.             self.Label:SetText( "Getting data: " .. datatype .. " - " .. string.format("%.2f", (self.value / self.max) * 100) .. "%" )
  4642.             if self.value == self.max then
  4643.                 xgui.dataInitialized = true
  4644.                 xgui.expectingdata = nil
  4645.                 self.Label:SetText( "Waiting for clientside processing..." )
  4646.                 xgui.queueFunctionCall( xgui.chunkbox.SetVisible, "chunkbox", xgui.chunkbox, false )
  4647.                 RunConsoleCommand( "_xgui", "dataComplete" )
  4648.             end
  4649.         end
  4650.     end
  4651.  
  4652.     ------------------------
  4653.     --XGUI QueueFunctionCall
  4654.     ------------------------
  4655.     --This is essentially a straight copy of Megiddo's queueFunctionCall; Since XGUI tends to use it quite a lot, I decided to seperate it to prevent delays in ULib's stuff
  4656.     --I also now get to add a method of flushing the queue based on a tag in the event that new data needs to be updated.
  4657.     local stack = {}
  4658.     local function onThink()
  4659.  
  4660.         local num = #stack
  4661.         if num > 0 then
  4662.             for i=1,3 do --Run 3 lines per frame
  4663.                 if stack[1] ~= nil then
  4664.                     local b, e = pcall( stack[ 1 ].fn, unpack( stack[ 1 ], 1, stack[ 1 ].n ) )
  4665.                     if not b then
  4666.                         ErrorNoHalt( "XGUI queue error: " .. tostring( e ) .. "\n" )
  4667.                     end
  4668.                 end
  4669.             table.remove( stack, 1 ) -- Remove the first inserted item. This is FIFO
  4670.             end
  4671.         else
  4672.             hook.Remove( "Think", "XGUIQueueThink" )
  4673.         end
  4674.     end
  4675.  
  4676.     function xgui.queueFunctionCall( fn, tag, ... )
  4677.         if type( fn ) ~= "function" then
  4678.             error( "queueFunctionCall received a bad function", 2 )
  4679.             return
  4680.         end
  4681.  
  4682.         table.insert( stack, { fn=fn, tag=tag, n=select( "#", ... ), ... } )
  4683.         hook.Add( "Think", "XGUIQueueThink", onThink, HOOK_MONITOR_HIGH )
  4684.     end
  4685.  
  4686.     function xgui.flushQueue( tag )
  4687.         local removeIndecies = {}
  4688.         for i, fncall in ipairs( stack ) do
  4689.             if fncall.tag == tag then
  4690.                 table.insert( removeIndecies, i )
  4691.             end
  4692.         end
  4693.         for i=#removeIndecies,1,-1 do --Remove the queue functions backwards to prevent desynchronization of pairs
  4694.             table.remove( stack, removeIndecies[i] )
  4695.         end
  4696.     end
  4697.  
  4698.  
  4699.     -------------------
  4700.     --ULIB XGUI helpers
  4701.     -------------------
  4702.     --Helper function to parse access tag for a particular argument
  4703.     function ulx.getTagArgNum( tag, argnum )
  4704.         return tag and ULib.splitArgs( tag, "<", ">" )[argnum]
  4705.     end
  4706.  
  4707.     --Load control interpretations for ULib argument types
  4708.     function ULib.cmds.BaseArg.x_getcontrol( arg, argnum, parent )
  4709.         return xlib.makelabel{ label="Not Supported", parent=parent }
  4710.     end
  4711.  
  4712.     function ULib.cmds.NumArg.x_getcontrol( arg, argnum, parent )
  4713.         local access, tag = LocalPlayer():query( arg.cmd )
  4714.         local restrictions = {}
  4715.         ULib.cmds.NumArg.processRestrictions( restrictions, arg, ulx.getTagArgNum( tag, argnum ) )
  4716.  
  4717.         if table.HasValue( arg, ULib.cmds.allowTimeString ) then
  4718.             local min = restrictions.min or 0
  4719.             local max = restrictions.max or 10 * 60 * 24 * 365 --default slider max 10 years
  4720.  
  4721.             local outPanel = xlib.makepanel{ h=40, parent=parent }
  4722.             xlib.makelabel{ x=5, y=3, label="Ban Length:", parent=outPanel }
  4723.             outPanel.interval = xlib.makecombobox{ x=90, w=75, parent=outPanel }
  4724.             outPanel.val = xlib.makeslider{ w=165, y=20, label="<--->", min=min, max=max, value=min, decimal=0, parent=outPanel }
  4725.  
  4726.             local divisor = {}
  4727.             local sensiblemax = {}
  4728.             if min == 0 then outPanel.interval:AddChoice( "Permanent" ) table.insert( divisor, 1 ) table.insert( sensiblemax, 0 ) end
  4729.             if max >= 1 and min <= 60*24 then outPanel.interval:AddChoice( "Minutes" ) table.insert( divisor, 1 ) table.insert( sensiblemax, 60*24 ) end
  4730.             if max >= 60 and min <= 60*24*7 then outPanel.interval:AddChoice( "Hours" ) table.insert( divisor, 60 ) table.insert( sensiblemax, 24*7 ) end
  4731.             if max >= ( 60*24 ) and min <= 60*24*120 then outPanel.interval:AddChoice( "Days" ) table.insert( divisor, 60*24 ) table.insert( sensiblemax, 120 ) end
  4732.             if max >= ( 60*24*7 ) and min <= 60*24*7*52 then outPanel.interval:AddChoice( "Weeks" ) table.insert( divisor, 60*24*7 ) table.insert( sensiblemax, 52 ) end
  4733.             if max >= ( 60*24*365 ) then outPanel.interval:AddChoice( "Years" ) table.insert( divisor, 60*24*365 ) table.insert( sensiblemax, 10 ) end
  4734.  
  4735.             outPanel.interval.OnSelect = function( self, index, value, data )
  4736.                 outPanel.val:SetDisabled( value == "Permanent" )
  4737.                 outPanel.val.maxvalue = math.min( max / divisor[index], sensiblemax[index] )
  4738.                 outPanel.val.minvalue = math.max( min / divisor[index], 0 )
  4739.                 outPanel.val:SetMax( outPanel.val.maxvalue )
  4740.                 outPanel.val:SetMin( outPanel.val.minvalue )
  4741.                 outPanel.val:SetValue( math.Clamp( tonumber( outPanel.val:GetValue() ), outPanel.val.minvalue, outPanel.val.maxvalue ) )
  4742.             end
  4743.  
  4744.             function outPanel.val:ValueChanged( val )
  4745.                 val = math.Clamp( tonumber( val ) or 0, self.minvalue or 0, self.maxvalue or 0 )
  4746.                 self.Slider:SetSlideX( self.Scratch:GetFraction( val ) )
  4747.                 if ( self.TextArea ~= vgui.GetKeyboardFocus() ) then
  4748.                     self.TextArea:SetValue( self.Scratch:GetTextValue() )
  4749.                 end
  4750.                 self:OnValueChanged( val )
  4751.             end
  4752.  
  4753.             if #outPanel.interval.Choices ~= 0 then
  4754.                 outPanel.interval:ChooseOptionID( 1 )
  4755.             end
  4756.  
  4757.             outPanel.GetValue = function( self )
  4758.                 local val, char = self:GetRawValue()
  4759.                 return val .. char
  4760.             end
  4761.             outPanel.GetRawValue = function( self )
  4762.                 local char = string.lower( self.interval:GetValue():sub(1,1) )
  4763.                 if char == "m" or char == "p" or tonumber( self.val:GetValue() ) == 0 then char = "" end
  4764.                 return self.val:GetValue(), char
  4765.             end
  4766.             outPanel.GetMinutes = function( self )
  4767.                 local btime, char = self:GetRawValue()
  4768.                 if char == "h" then btime = btime * 60
  4769.                 elseif char == "d" then btime = btime * 1440
  4770.                 elseif char == "w" then btime = btime * 10080
  4771.                 elseif char == "y" then btime = btime * 525600 end
  4772.                 return btime
  4773.             end
  4774.             outPanel.TextArea = outPanel.val.TextArea
  4775.             return outPanel
  4776.         else
  4777.             local defvalue = arg.min
  4778.             if table.HasValue( arg, ULib.cmds.optional ) then defvalue = arg.default end
  4779.             if not defvalue then defvalue = 0 end --No default was set for this command, so we'll use 0.
  4780.  
  4781.             local maxvalue = restrictions.max
  4782.             local minvalue = restrictions.min or 0
  4783.             if maxvalue == nil then
  4784.                 if defvalue > 100 then
  4785.                     maxvalue = defvalue
  4786.                 else
  4787.                     maxvalue = 100
  4788.                 end
  4789.             end
  4790.  
  4791.             local decimal = 0
  4792.             if not table.HasValue( arg, ULib.cmds.round ) then
  4793.                 local minMaxDelta = maxvalue - minvalue
  4794.                 if minMaxDelta < 5 then
  4795.                     decimal = 2
  4796.                 elseif minMaxDelta <= 10 then
  4797.                     decimal = 1
  4798.                 end
  4799.             end
  4800.  
  4801.             local outPanel = xlib.makepanel{ h=35, parent=parent }
  4802.             xlib.makelabel{ label=arg.hint or "NumArg", parent=outPanel }
  4803.             outPanel.val = xlib.makeslider{ y=15, w=165, min=minvalue, max=maxvalue, value=defvalue, decimal=decimal, label="<--->", parent=outPanel }
  4804.             outPanel.GetValue = function( self ) return outPanel.val.GetValue( outPanel.val ) end
  4805.             outPanel.TextArea = outPanel.val.TextArea
  4806.             return outPanel
  4807.         end
  4808.     end
  4809.  
  4810.     function ULib.cmds.NumArg.getTime( arg )
  4811.         if arg == nil or arg == "" then return nil, nil end
  4812.  
  4813.         if arg == 0 or tonumber( arg ) == 0 then
  4814.             return "Permanent", 0
  4815.         end
  4816.  
  4817.         local charPriority = { "y", "w", "d", "h" }
  4818.         local charMap = { "Years", "Weeks", "Days", "Hours" }
  4819.         local divisor = { 60 * 24 * 365, 60 * 24 * 7, 60 * 24, 60 }
  4820.         for i, v in ipairs( charPriority ) do
  4821.             if arg:find( v, 1, true ) then
  4822.                 if not charMap[ i ] or not divisor [ i ] or not ULib.stringTimeToMinutes( arg ) then return nil, nil end
  4823.                 local val = ULib.stringTimeToMinutes( arg ) / divisor[ i ]
  4824.                 if val == 0 then return "Permanent", 0 end
  4825.                 return charMap[ i ], val
  4826.             end
  4827.         end
  4828.  
  4829.         return "Minutes", ULib.stringTimeToMinutes( arg )
  4830.     end
  4831.  
  4832.  
  4833.     function ULib.cmds.StringArg.x_getcontrol( arg, argnum, parent )
  4834.         local access, tag = LocalPlayer():query( arg.cmd )
  4835.         local restrictions = {}
  4836.         ULib.cmds.StringArg.processRestrictions( restrictions, arg, ulx.getTagArgNum( tag, argnum ) )
  4837.  
  4838.         local is_restricted_to_completes = table.HasValue( arg, ULib.cmds.restrictToCompletes ) -- Program-level restriction (IE, ulx map)
  4839.             or restrictions.playerLevelRestriction -- The player's tag specifies only certain strings
  4840.  
  4841.         if is_restricted_to_completes then
  4842.             return xlib.makecombobox{ text=arg.hint or "StringArg", choices=restrictions.restrictedCompletes, parent=parent }
  4843.         elseif restrictions.restrictedCompletes and table.Count( restrictions.restrictedCompletes ) > 0 then
  4844.             -- This is where there needs to be both a drop down AND an input box
  4845.             local outPanel = xlib.makecombobox{ text=arg.hint, choices=restrictions.restrictedCompletes, enableinput=true, selectall=true, parent=parent }
  4846.             outPanel.OnEnter = function( self )
  4847.                 self:GetParent():OnEnter()
  4848.             end
  4849.             return outPanel
  4850.         else
  4851.             return xlib.maketextbox{ text=arg.hint or "StringArg", selectall=true, parent=parent }
  4852.         end
  4853.     end
  4854.  
  4855.     function ULib.cmds.PlayerArg.x_getcontrol( arg, argnum, parent )
  4856.         local access, tag = LocalPlayer():query( arg.cmd )
  4857.         local restrictions = {}
  4858.         ULib.cmds.PlayerArg.processRestrictions( restrictions, LocalPlayer(), arg, ulx.getTagArgNum( tag, argnum ) )
  4859.  
  4860.         local outPanel = xlib.makecombobox{ text=arg.hint, parent=parent }
  4861.         local targets = restrictions.restrictedTargets
  4862.         if targets == false then -- No one allowed
  4863.             targets = {}
  4864.         elseif targets == nil then -- Everyone allowed
  4865.             targets = player.GetAll()
  4866.         end
  4867.  
  4868.         for _, ply in ipairs( targets ) do
  4869.             outPanel:AddChoice( ply:Nick() )
  4870.         end
  4871.         return outPanel
  4872.     end
  4873.  
  4874.     function ULib.cmds.CallingPlayerArg.x_getcontrol( arg, argnum, parent )
  4875.         return xlib.makelabel{ label=arg.hint or "CallingPlayer", parent=parent }
  4876.     end
  4877.  
  4878.     function ULib.cmds.BoolArg.x_getcontrol( arg, argnum, parent )
  4879.         local access, tag = LocalPlayer():query( arg.cmd )
  4880.         local restrictions = {}
  4881.         ULib.cmds.BoolArg.processRestrictions( restrictions, arg, ulx.getTagArgNum( tag, argnum ) )
  4882.  
  4883.         local outPanel = xlib.makecheckbox{ label=arg.hint or "BoolArg", value=restrictions.restrictedTo, parent=parent }
  4884.         if restrictions.restrictedTo ~= nil then outPanel:SetDisabled( true ) end
  4885.         outPanel.GetValue = function( self )
  4886.             return self:GetChecked() and 1 or 0
  4887.         end
  4888.         return outPanel
  4889.     end
  4890. end
  4891.  
  4892. --XGUI: A GUI for ULX -- by Stickly Man!
  4893. xgui = xgui or {}
  4894.  
  4895. --Make a spot for modules to store data and hooks
  4896. xgui.data = xgui.data or {}
  4897. xgui.hook = xgui.hook or { onProcessModules={}, onOpen={}, onClose={} }
  4898. --Call this function in your client-side module code to ensure the data types have been instantiated on the client.
  4899. function xgui.prepareDataType( dtype, location )
  4900.     if not xgui.data[dtype] then
  4901.         xgui.data[dtype] = location or {}
  4902.         xgui.hook[dtype] = { clear={}, process={}, done={}, add={}, update={}, remove={}, data={} }
  4903.     end
  4904. end
  4905.  
  4906. --Set up various hooks modules can "hook" into.
  4907. function xgui.hookEvent( dtype, event, func, name )
  4908.     if not xgui.hook[dtype] or ( event and not xgui.hook[dtype][event] ) then
  4909.         Msg( "XGUI: Attempted to add to invalid type or event to a hook! (" .. dtype .. ", " .. ( event or "nil" ) .. ")\n" )
  4910.     else
  4911.         if not name then name = "FixMe" .. math.floor(math.random()*10000) end -- Backwards compatibility for older XGUI modules
  4912.         if not event then
  4913.             xgui.hook[dtype][name] = func
  4914.         else
  4915.             xgui.hook[dtype][event][name] = func
  4916.         end
  4917.     end
  4918. end
  4919.  
  4920. --Set up tables and functions for creating and storing modules
  4921. xgui.modules = xgui.modules or {}
  4922.  
  4923. xgui.modules.tab = xgui.modules.tab or {}
  4924. function xgui.addModule( name, panel, icon, access, tooltip )
  4925.     local refreshModules = false
  4926.     for i = #xgui.modules.tab, 1, -1 do
  4927.         if xgui.modules.tab[i].name == name then
  4928.             xgui.modules.tab[i].panel:Remove()
  4929.             xgui.modules.tab[i].tabpanel:Remove()
  4930.             xgui.modules.tab[i].xbutton:Remove()
  4931.             table.remove(xgui.modules.tab, i)
  4932.             refreshModules = true
  4933.         end
  4934.     end
  4935.     table.insert( xgui.modules.tab, { name=name, panel=panel, icon=icon, access=access, tooltip=tooltip } )
  4936.     if refreshModules then xgui.processModules() end
  4937. end
  4938.  
  4939. xgui.modules.setting = xgui.modules.setting or {}
  4940. function xgui.addSettingModule( name, panel, icon, access, tooltip )
  4941.     local refreshModules = false
  4942.     for i = #xgui.modules.setting, 1, -1 do
  4943.         if xgui.modules.setting[i].name == name then
  4944.             xgui.modules.setting[i].panel:Remove()
  4945.             xgui.modules.setting[i].tabpanel:Remove()
  4946.             table.remove(xgui.modules.setting, i)
  4947.             refreshModules = true
  4948.         end
  4949.     end
  4950.     table.insert( xgui.modules.setting, { name=name, panel=panel, icon=icon, access=access, tooltip=tooltip } )
  4951.     if refreshModules then xgui.processModules() end
  4952. end
  4953.  
  4954. xgui.modules.submodule = xgui.modules.submodule or {}
  4955. function xgui.addSubModule( name, panel, access, mtype )
  4956.     local refreshModules = false
  4957.     for i = #xgui.modules.submodule, 1, -1 do
  4958.         if xgui.modules.submodule[i].name == name then
  4959.             xgui.modules.submodule[i].panel:Remove()
  4960.             table.remove(xgui.modules.submodule, i)
  4961.             refreshModules = true
  4962.         end
  4963.     end
  4964.     table.insert( xgui.modules.submodule, { name=name, panel=panel, access=access, mtype=mtype } )
  4965.     if refreshModules then xgui.processModules() end
  4966. end
  4967. --Set up a spot to store entries for autocomplete.
  4968. xgui.tabcompletes = xgui.tabcompletes or {}
  4969. xgui.ulxmenucompletes = xgui.ulxmenucompletes or {}
  4970.  
  4971.  
  4972. --Set up XGUI clientside settings, load settings from file if it exists
  4973. xgui.settings = xgui.settings or {}
  4974. if ULib.fileExists( "data/ulx/xgui_settings.txt" ) then
  4975.     local input = ULib.fileRead( "data/ulx/xgui_settings.txt" )
  4976.     input = input:match( "^.-\n(.*)$" )
  4977.     xgui.settings = ULib.parseKeyValues( input )
  4978. end
  4979. --Set default settings if they didn't get loaded
  4980. if not xgui.settings.moduleOrder then xgui.settings.moduleOrder = { "Cmds", "Groups", "Maps", "Settings", "Bans" } end
  4981. if not xgui.settings.settingOrder then xgui.settings.settingOrder = { "Sandbox", "Server", "Client" } end
  4982. if not xgui.settings.animTime then xgui.settings.animTime = 0.22 else xgui.settings.animTime = tonumber( xgui.settings.animTime ) end
  4983. if not xgui.settings.infoColor then
  4984.     --Default color
  4985.     xgui.settings.infoColor = Color( 100, 255, 255, 128 )
  4986. else
  4987.     --Ensure that the color contains numbers, not strings
  4988.     xgui.settings.infoColor = Color(xgui.settings.infoColor.r, xgui.settings.infoColor.g, xgui.settings.infoColor.b, xgui.settings.infoColor.a)
  4989. end
  4990. if not xgui.settings.showLoadMsgs then xgui.settings.showLoadMsgs = true else xgui.settings.showLoadMsgs = ULib.toBool( xgui.settings.showLoadMsgs ) end
  4991. if not xgui.settings.skin then xgui.settings.skin = "Default" end
  4992. if not xgui.settings.xguipos then xgui.settings.xguipos = { pos=5, xoff=0, yoff=0 } end
  4993. if not xgui.settings.animIntype then xgui.settings.animIntype = 1 end
  4994. if not xgui.settings.animOuttype then xgui.settings.animOuttype = 1 end
  4995.  
  4996.  
  4997. function xgui.init( ply )
  4998.     xgui.load_helpers()
  4999.  
  5000.     --Initiate the base window (see xgui_helpers.lua for code)
  5001.     xgui.makeXGUIbase{}
  5002.  
  5003.     --Create the bottom infobar
  5004.     xgui.infobar = xlib.makepanel{ x=10, y=399, w=580, h=20, parent=xgui.anchor }
  5005.     xgui.infobar:NoClipping( true )
  5006.     xgui.infobar.Paint = function( self, w, h )
  5007.         draw.RoundedBoxEx( 4, 0, 1, 580, 20, xgui.settings.infoColor, false, false, true, true )
  5008.     end
  5009.     local infoLabel = string.format( "\nULX Admin Mod :: XGUI - Team Ulysses |  ULX %s  |  ULib %s", ULib.pluginVersionStr("ULX"), ULib.pluginVersionStr("ULib") )
  5010.     xlib.makelabel{ x=5, y=-10, label=infoLabel, parent=xgui.infobar }:NoClipping( true )
  5011.     xgui.thetime = xlib.makelabel{ x=515, y=-10, label="", parent=xgui.infobar }
  5012.     xgui.thetime:NoClipping( true )
  5013.     xgui.thetime.check = function()
  5014.         xgui.thetime:SetText( os.date( "\n%I:%M:%S %p" ) )
  5015.         xgui.thetime:SizeToContents()
  5016.         timer.Simple( 1, xgui.thetime.check )
  5017.     end
  5018.     xgui.thetime.check()
  5019.  
  5020.     --Create an offscreen place to parent modules that the player can't access
  5021.     xgui.null = xlib.makepanel{ x=-10, y=-10, w=0, h=0 }
  5022.     xgui.null:SetVisible( false )
  5023.  
  5024.     --Load modules
  5025.     local sm = xgui.settings.showLoadMsgs
  5026.     if sm then
  5027.         Msg( "\n///////////////////////////////////////\n" )
  5028.         Msg( "//  ULX GUI -- Made by Stickly Man!  //\n" )
  5029.         Msg( "///////////////////////////////////////\n" )
  5030.         Msg( "// Loading GUI Modules...            //\n" )
  5031.     end
  5032.     for _, file in ipairs( file.Find( "ulx/xgui/*.lua", "LUA" ) ) do
  5033.         include( "ulx/xgui/" .. file )
  5034.         if sm then Msg( "//   " .. file .. string.rep( " ", 32 - file:len() ) .. "//\n" ) end
  5035.     end
  5036.     if sm then Msg( "// Loading Setting Modules...        //\n" ) end
  5037.     for _, file in ipairs( file.Find( "ulx/xgui/settings/*.lua", "LUA" ) ) do
  5038.         include( "ulx/xgui/settings/" .. file )
  5039.         if sm then Msg( "//   " .. file .. string.rep( " ", 32 - file:len() ) .. "//\n" ) end
  5040.     end
  5041.     if sm then Msg( "// Loading Gamemode Module(s)...     //\n" ) end
  5042.     if ULib.isSandbox() and GAMEMODE.FolderName ~= "sandbox" then -- If the gamemode sandbox-derived (but not sandbox, that will get added later), then add the sandbox Module
  5043.         include( "ulx/xgui/gamemodes/sandbox.lua" )
  5044.         if sm then Msg( "//   sandbox.lua                     //\n" ) end
  5045.     end
  5046.     for _, file in ipairs( file.Find( "ulx/xgui/gamemodes/*.lua", "LUA" ) ) do
  5047.         if string.lower( file ) == string.lower( GAMEMODE.FolderName .. ".lua" ) then
  5048.             include( "ulx/xgui/gamemodes/" .. file )
  5049.             if sm then Msg( "//   " .. file .. string.rep( " ", 32 - file:len() ) .. "//\n" ) end
  5050.             break
  5051.         end
  5052.         if sm then Msg( "//   No module found!                //\n" ) end
  5053.     end
  5054.     if sm then Msg( "// Modules Loaded!                   //\n" ) end
  5055.     if sm then Msg( "///////////////////////////////////////\n\n" ) end
  5056.  
  5057.     --Find any existing modules that aren't listed in the requested order.
  5058.     local function checkModulesOrder( moduleTable, sortTable )
  5059.         for _, m in ipairs( moduleTable ) do
  5060.             local notlisted = true
  5061.             for _, existing in ipairs( sortTable ) do
  5062.                 if m.name == existing then
  5063.                     notlisted = false
  5064.                     break
  5065.                 end
  5066.             end
  5067.             if notlisted then
  5068.                 table.insert( sortTable, m.name )
  5069.             end
  5070.         end
  5071.     end
  5072.     checkModulesOrder( xgui.modules.tab, xgui.settings.moduleOrder )
  5073.     checkModulesOrder( xgui.modules.setting, xgui.settings.settingOrder )
  5074.  
  5075.     --Check if the server has XGUI installed
  5076.     RunConsoleCommand( "_xgui", "getInstalled" )
  5077.  
  5078.     xgui.initialized = true
  5079.  
  5080.     xgui.processModules()
  5081. end
  5082. hook.Add( ULib.HOOK_LOCALPLAYERREADY, "InitXGUI", xgui.init, HOOK_MONITOR_LOW )
  5083.  
  5084. function xgui.saveClientSettings()
  5085.     if not ULib.fileIsDir( "data/ulx" ) then
  5086.         ULib.fileCreateDir( "data/ulx" )
  5087.     end
  5088.     local output = "// This file stores clientside settings for XGUI.\n"
  5089.     output = output .. ULib.makeKeyValues( xgui.settings )
  5090.     ULib.fileWrite( "data/ulx/xgui_settings.txt", output )
  5091. end
  5092.  
  5093. function xgui.checkModuleExists( modulename, moduletable )
  5094.     for k, v in ipairs( moduletable ) do
  5095.         if v.name == modulename then
  5096.             return k
  5097.         end
  5098.     end
  5099.     return false
  5100. end
  5101.  
  5102. function xgui.processModules()
  5103.     local activetab = nil
  5104.     if xgui.base:GetActiveTab() then
  5105.         activetab = xgui.base:GetActiveTab():GetValue()
  5106.     end
  5107.  
  5108.     local activesettingstab = nil
  5109.     if xgui.settings_tabs:GetActiveTab() then
  5110.         activesettingstab = xgui.settings_tabs:GetActiveTab():GetValue()
  5111.     end
  5112.  
  5113.     xgui.base:Clear() --We need to remove any existing tabs in the GUI
  5114.     xgui.tabcompletes = {}
  5115.     xgui.ulxmenucompletes = {}
  5116.     for _, modname in ipairs( xgui.settings.moduleOrder ) do
  5117.         local module = xgui.checkModuleExists( modname, xgui.modules.tab )
  5118.         if module then
  5119.             module = xgui.modules.tab[module]
  5120.             if module.xbutton == nil then
  5121.                 module.xbutton = xlib.makebutton{ x=555, y=-5, w=32, h=24, btype="close", parent=module.panel }
  5122.                 module.xbutton.DoClick = function()
  5123.                     xgui.hide()
  5124.                 end
  5125.             end
  5126.             if LocalPlayer():query( module.access ) then
  5127.                 xgui.base:AddSheet( module.name, module.panel, module.icon, false, false, module.tooltip )
  5128.                 module.tabpanel = xgui.base.Items[#xgui.base.Items].Tab
  5129.                 table.insert( xgui.tabcompletes, "xgui show " .. modname )
  5130.                 table.insert( xgui.ulxmenucompletes, "ulx menu " .. modname )
  5131.             else
  5132.                 module.tabpanel = nil
  5133.                 module.panel:SetParent( xgui.null )
  5134.             end
  5135.         end
  5136.     end
  5137.  
  5138.     xgui.settings_tabs:Clear() --Clear out settings tabs for reprocessing
  5139.     for _, modname in ipairs( xgui.settings.settingOrder ) do
  5140.         local module = xgui.checkModuleExists( modname, xgui.modules.setting )
  5141.         if module then
  5142.             module = xgui.modules.setting[module]
  5143.             if LocalPlayer():query( module.access ) then
  5144.                 xgui.settings_tabs:AddSheet( module.name, module.panel, module.icon, false, false, module.tooltip )
  5145.                 module.tabpanel = xgui.settings_tabs.Items[#xgui.settings_tabs.Items].Tab
  5146.                 table.insert( xgui.tabcompletes, "xgui show " .. modname )
  5147.                 table.insert( xgui.ulxmenucompletes, "ulx menu " .. modname )
  5148.             else
  5149.                 module.tabpanel = nil
  5150.                 module.panel:SetParent( xgui.null )
  5151.             end
  5152.         end
  5153.     end
  5154.  
  5155.     --Call any functions that requested to be called when permissions change
  5156.     xgui.callUpdate( "onProcessModules" )
  5157.     table.sort( xgui.tabcompletes )
  5158.     table.sort( xgui.ulxmenucompletes )
  5159.  
  5160.     local hasFound = false
  5161.     if activetab then
  5162.         for _, v in pairs( xgui.base.Items ) do
  5163.             if v.Tab:GetValue() == activetab then
  5164.                 xgui.base:SetActiveTab( v.Tab, true )
  5165.                 hasFound = true
  5166.                 break
  5167.             end
  5168.         end
  5169.         if not hasFound then
  5170.             xgui.base.m_pActiveTab = "none"
  5171.             xgui.base:SetActiveTab( xgui.base.Items[1].Tab, true )
  5172.         end
  5173.     end
  5174.  
  5175.     hasFound = false
  5176.     if activesettingstab then
  5177.         for _, v in pairs( xgui.settings_tabs.Items ) do
  5178.             if v.Tab:GetValue() == activesettingstab then
  5179.                 xgui.settings_tabs:SetActiveTab( v.Tab, true )
  5180.                 hasFound = true
  5181.                 break
  5182.             end
  5183.         end
  5184.         if not hasFound then
  5185.             xgui.settings_tabs.m_pActiveTab = "none"
  5186.             xgui.settings_tabs:SetActiveTab( xgui.settings_tabs.Items[1].Tab, true )
  5187.         end
  5188.     end
  5189. end
  5190.  
  5191. function xgui.checkNotInstalled( tabname )
  5192.     if xgui.notInstalledWarning then return end
  5193.  
  5194.     gui.EnableScreenClicker( true )
  5195.     RestoreCursorPosition()
  5196.     xgui.notInstalledWarning = xlib.makeframe{ label="XGUI Warning!", w=375, h=110, nopopup=true, showclose=false, skin=xgui.settings.skin }
  5197.     xlib.makelabel{ x=10, y=30, wordwrap=true, w=365, label="XGUI has not initialized properly with the server. This could be caused by a heavy server load after a mapchange, a major error during XGUI server startup, or XGUI not being installed.", parent=xgui.notInstalledWarning }
  5198.  
  5199.     xlib.makebutton{ x=37, y=83, w=80, label="Offline Mode", parent=xgui.notInstalledWarning }.DoClick = function()
  5200.         xgui.notInstalledWarning:Remove()
  5201.         xgui.notInstalledWarning = nil
  5202.         offlineWarning = xlib.makeframe{ label="XGUI Warning!", w=375, h=110, nopopup=true, showclose=false, skin=xgui.settings.skin }
  5203.         xlib.makelabel{ x=10, y=30, wordwrap=true, w=365, label="XGUI will run locally in offline mode. Some features will not work, and information will be missing. You can attempt to reconnect to the server using the 'Refresh Server Data' button in the XGUI client menu.", parent=offlineWarning }
  5204.         xlib.makebutton{ x=77, y=83, w=80, label="OK", parent=offlineWarning }.DoClick = function()
  5205.             offlineWarning:Remove()
  5206.             xgui.offlineMode = true
  5207.             xgui.show( tabname )
  5208.         end
  5209.         xlib.makebutton{ x=217, y=83, w=80, label="Cancel", parent=offlineWarning }.DoClick = function()
  5210.             offlineWarning:Remove()
  5211.             RememberCursorPosition()
  5212.             gui.EnableScreenClicker( false )
  5213.         end
  5214.     end
  5215.  
  5216.     xlib.makebutton{ x=257, y=83, w=80, label="Close", parent=xgui.notInstalledWarning }.DoClick = function()
  5217.         xgui.notInstalledWarning:Remove()
  5218.         xgui.notInstalledWarning = nil
  5219.         RememberCursorPosition()
  5220.         gui.EnableScreenClicker( false )
  5221.     end
  5222.  
  5223.     xlib.makebutton{ x=147, y=83, w=80, label="Try Again", parent=xgui.notInstalledWarning }.DoClick = function()
  5224.         xgui.notInstalledWarning:Remove()
  5225.         xgui.notInstalledWarning = nil
  5226.         RememberCursorPosition()
  5227.         gui.EnableScreenClicker( false )
  5228.         local reattempt = xlib.makeframe{ label="XGUI: Attempting reconnection...", w=200, h=20, nopopup=true, showclose=false, skin=xgui.settings.skin }
  5229.         timer.Simple( 1, function()
  5230.             RunConsoleCommand( "_xgui", "getInstalled" )
  5231.             reattempt:Remove()
  5232.             timer.Simple( 0.5, function() xgui.show( tabname ) end )
  5233.         end )
  5234.     end
  5235. end
  5236.  
  5237. function xgui.show( tabname )
  5238.     if not xgui.anchor then return end
  5239.     if not xgui.initialized then return end
  5240.  
  5241.     --Check if XGUI is not installed, display the warning if hasn't been shown yet.
  5242.     if not xgui.isInstalled and not xgui.offlineMode then
  5243.         xgui.checkNotInstalled( tabname )
  5244.         return
  5245.     end
  5246.  
  5247.     if not game.SinglePlayer() and not ULib.ucl.authed[LocalPlayer():UniqueID()] then
  5248.         local unauthedWarning = xlib.makeframe{ label="XGUI Error!", w=250, h=90, showclose=true, skin=xgui.settings.skin }
  5249.         xlib.makelabel{ label="Your ULX player has not been Authed!", x=10, y=30, parent=unauthedWarning }
  5250.         xlib.makelabel{ label="Please wait a couple seconds and try again.", x=10, y=45, parent=unauthedWarning }
  5251.         xlib.makebutton{ x=50, y=63, w=60, label="Try Again", parent=unauthedWarning }.DoClick = function()
  5252.             unauthedWarning:Remove()
  5253.             xgui.show( tabname )
  5254.         end
  5255.         xlib.makebutton{ x=140, y=63, w=60, label="Close", parent=unauthedWarning }.DoClick = function()
  5256.             unauthedWarning:Remove()
  5257.         end
  5258.         return
  5259.     end
  5260.  
  5261.     if xgui.base.refreshSkin then
  5262.         xgui.base:SetSkin( xgui.settings.skin )
  5263.         xgui.base.refreshSkin = nil
  5264.     end
  5265.  
  5266.     --In case the string name had spaces, it sent the whole argument table. Convert it to a string here!
  5267.     if type( tabname ) == "table" then
  5268.         tabname = table.concat( tabname, " " )
  5269.     end
  5270.     --Sets the active tab to tabname if it was specified
  5271.     if tabname and tabname ~= "" then
  5272.         local found, settingsTab
  5273.         for _, v in ipairs( xgui.modules.tab ) do
  5274.             if string.lower( v.name ) == "settings" then settingsTab = v.tabpanel end
  5275.             if string.lower( v.name ) == string.lower( tabname ) and v.panel:GetParent() ~= xgui.null then
  5276.                 xgui.base:SetActiveTab( v.tabpanel )
  5277.                 if xgui.anchor:IsVisible() then return end
  5278.                 found = true
  5279.                 break
  5280.             end
  5281.         end
  5282.         if not found then
  5283.             for _, v in ipairs( xgui.modules.setting ) do
  5284.                 if string.lower( v.name ) == string.lower( tabname ) and v.panel:GetParent() ~= xgui.null then
  5285.                     xgui.base:SetActiveTab( settingsTab )
  5286.                     xgui.settings_tabs:SetActiveTab( v.tabpanel )
  5287.                     if xgui.anchor:IsVisible() then return end
  5288.                     found = true
  5289.                     break
  5290.                 end
  5291.             end
  5292.         end
  5293.         if not found then return end --If invalid input was taken, then do nothing.
  5294.     end
  5295.  
  5296.     xgui.base.animOpen()
  5297.     gui.EnableScreenClicker( true )
  5298.     RestoreCursorPosition()
  5299.     xgui.anchor:SetMouseInputEnabled( true )
  5300.  
  5301.     --Calls the functions requesting to hook when XGUI is opened
  5302.     xgui.callUpdate( "onOpen" )
  5303. end
  5304.  
  5305. function xgui.hide()
  5306.     if not xgui.anchor then return end
  5307.     if not xgui.anchor:IsVisible() then return end
  5308.     RememberCursorPosition()
  5309.     gui.EnableScreenClicker( false )
  5310.     xgui.anchor:SetMouseInputEnabled( false )
  5311.     xgui.base.animClose()
  5312.     CloseDermaMenus()
  5313.  
  5314.     --Calls the functions requesting to hook when XGUI is closed
  5315.     xgui.callUpdate( "onClose" )
  5316. end
  5317.  
  5318. function xgui.toggle( tabname )
  5319.     if xgui.anchor and ( not xgui.anchor:IsVisible() or ( tabname and #tabname ~= 0 ) ) then
  5320.         xgui.show( tabname )
  5321.     else
  5322.         xgui.hide()
  5323.     end
  5324. end
  5325.  
  5326. --New XGUI Data stuff
  5327. function xgui.expectChunks( numofchunks )
  5328.     if xgui.isInstalled then
  5329.         xgui.expectingdata = true
  5330.         xgui.chunkbox.max = numofchunks
  5331.         xgui.chunkbox.value = 0
  5332.         xgui.chunkbox:SetFraction( 0 )
  5333.         xgui.chunkbox.Label:SetText( "Getting data: Waiting for server..." )
  5334.         xgui.chunkbox:SetVisible( true )
  5335.         xgui.chunkbox:SetSkin( xgui.settings.skin )
  5336.         xgui.flushQueue( "chunkbox" ) --Remove the queue entry that would hide the chunkbox
  5337.     end
  5338. end
  5339.  
  5340. function xgui.getChunk( flag, datatype, data )
  5341.     if xgui.expectingdata then
  5342.         --print( datatype, flag ) --Debug
  5343.         if flag == -1 then
  5344.             --Ignore these chunks
  5345.         elseif flag == 0 then --Data should be purged
  5346.             if xgui.data[datatype] then
  5347.                 table.Empty( xgui.data[datatype] )
  5348.             end
  5349.             xgui.flushQueue( datatype )
  5350.             xgui.callUpdate( datatype, "clear" )
  5351.         elseif flag == 1 then
  5352.             if not xgui.mergeData then --A full data table is coming in
  5353.                 if not data then data = {} end --Failsafe for no table being sent
  5354.                 xgui.flushQueue( datatype )
  5355.                 table.Empty( xgui.data[datatype] )
  5356.                 table.Merge( xgui.data[datatype], data )
  5357.                 xgui.callUpdate( datatype, "clear" )
  5358.                 xgui.callUpdate( datatype, "process", data )
  5359.                 xgui.callUpdate( datatype, "done" )
  5360.             else --A chunk of data is coming in
  5361.                 table.Merge( xgui.data[datatype], data )
  5362.                 xgui.callUpdate( datatype, "process", data )
  5363.             end
  5364.         elseif flag == 2 or flag == 3 then --Add/Update a portion of data
  5365.             table.Merge( xgui.data[datatype], data )
  5366.             xgui.callUpdate( datatype, flag == 2 and "add" or "update", data )
  5367.         elseif flag == 4 then --Remove a key from the table
  5368.             xgui.removeDataEntry( xgui.data[datatype], data ) --Needs to be called recursively!
  5369.             xgui.callUpdate( datatype, "remove", data )
  5370.         elseif flag == 5 then --Begin a set of chunks (Clear the old data, then flag to merge incoming data)
  5371.             table.Empty( xgui.data[datatype] )
  5372.             xgui.mergeData = true
  5373.             xgui.flushQueue( datatype )
  5374.             xgui.callUpdate( datatype, "clear" )
  5375.         elseif flag == 6 then --End a set of chunks (Clear the merge flag)
  5376.             xgui.mergeData = nil
  5377.             xgui.callUpdate( datatype, "done" )
  5378.         elseif flag == 7 then --Pass the data directly to the module to be handled.
  5379.             xgui.callUpdate( datatype, "data", data )
  5380.         end
  5381.         xgui.chunkbox:Progress( datatype )
  5382.     end
  5383. end
  5384.  
  5385. function xgui.removeDataEntry( data, entry )
  5386.     for k, v in pairs( entry ) do
  5387.         if type( v ) == "table" then
  5388.             xgui.removeDataEntry( data[k], v )
  5389.         else
  5390.             if type(v) == "number" then
  5391.                 table.remove( data, v )
  5392.             else
  5393.                 data[v] = nil
  5394.             end
  5395.         end
  5396.     end
  5397. end
  5398.  
  5399. function xgui.callUpdate( dtype, event, data )
  5400.     --Run any functions that request to be called when "curtable" is updated
  5401.     if not xgui.hook[dtype] or ( event and not xgui.hook[dtype][event] ) then
  5402.         Msg( "XGUI: Attempted to call non-existent type or event to a hook! (" .. dtype .. ", " .. ( event or "nil" ) .. ")\n" )
  5403.     else
  5404.         if not event then
  5405.             for name, func in pairs( xgui.hook[dtype] ) do func( data ) end
  5406.         else
  5407.             for name, func in pairs( xgui.hook[dtype][event] ) do func( data ) end
  5408.         end
  5409.     end
  5410. end
  5411.  
  5412. --If the player's group is changed, reprocess the XGUI modules for permissions, and request for extra data if needed
  5413. function xgui.PermissionsChanged( ply )
  5414.     if ply == LocalPlayer() and xgui.isInstalled and xgui.dataInitialized then
  5415.         xgui.processModules()
  5416.         local types = {}
  5417.         for dtype, data in pairs( xgui.data ) do
  5418.             if table.Count( data ) > 0 then table.insert( types, dtype ) end
  5419.         end
  5420.         RunConsoleCommand( "xgui", "refreshdata", unpack( types ) )
  5421.     end
  5422. end
  5423. hook.Add( "UCLAuthed", "XGUI_PermissionsChanged", xgui.PermissionsChanged )
  5424.  
  5425. function xgui.getInstalled()
  5426.     if not xgui.isInstalled then
  5427.         if xgui.notInstalledWarning then
  5428.             xgui.notInstalledWarning:Remove()
  5429.             xgui.notInstalledWarning = nil
  5430.         end
  5431.         xgui.isInstalled = true
  5432.         xgui.offlineMode = false
  5433.         RunConsoleCommand( "xgui", "getdata" )
  5434.     end
  5435. end
  5436.  
  5437. function xgui.cmd_base( ply, func, args )
  5438.     if not args[ 1 ] then
  5439.         xgui.toggle()
  5440.     elseif xgui.isInstalled then --First check that it's installed
  5441.         RunConsoleCommand( "_xgui", unpack( args ) )
  5442.     end
  5443. end
  5444.  
  5445. function xgui.tab_completes()
  5446.     return xgui.tabcompletes
  5447. end
  5448.  
  5449. function xgui.ulxmenu_tab_completes()
  5450.     return xgui.ulxmenucompletes
  5451. end
  5452.  
  5453. ULib.cmds.addCommandClient( "xgui", xgui.cmd_base )
  5454. ULib.cmds.addCommandClient( "xgui show", function( ply, cmd, args ) xgui.show( args ) end, xgui.tab_completes )
  5455. ULib.cmds.addCommandClient( "xgui hide", xgui.hide )
  5456. ULib.cmds.addCommandClient( "xgui toggle", function() xgui.toggle() end )
  5457.  
  5458. --local ulxmenu = ulx.command( CATEGORY_NAME, "ulx menu", ulx.menu, "!menu" )
  5459. ULib.cmds.addCommandClient( "ulx menu", function( ply, cmd, args ) xgui.toggle( args ) end, xgui.ulxmenu_tab_completes )
  5460.  
  5461. ulx.teams = ulx.teams or {}
  5462.  
  5463. function ulx.populateClTeams( teams )
  5464.     ulx.teams = teams
  5465.  
  5466.     for i=1, #teams do
  5467.         local team_data = teams[ i ]
  5468.         team.SetUp( team_data.index, team_data.name, team_data.color )
  5469.     end
  5470. end
  5471.  
  5472. ulx.motdmenu_exists = true
  5473.  
  5474. local mode
  5475. local url
  5476.  
  5477. function ulx.showMotdMenu( steamid )
  5478.     if mode == nil then
  5479.         return -- No data provided
  5480.     end
  5481.  
  5482.     local window = vgui.Create( "DFrame" )
  5483.     if ScrW() > 640 then -- Make it larger if we can.
  5484.         window:SetSize( ScrW()*0.9, ScrH()*0.9 )
  5485.     else
  5486.         window:SetSize( 640, 480 )
  5487.     end
  5488.     window:Center()
  5489.     window:SetTitle( "ULX MOTD" )
  5490.     window:SetVisible( true )
  5491.     window:MakePopup()
  5492.  
  5493.     local html = vgui.Create( "DHTML", window )
  5494.     --html:SetAllowLua( true ) -- Too much of a security risk for us to enable. Feel free to uncomment if you know what you're doing.
  5495.  
  5496.     local button = vgui.Create( "DButton", window )
  5497.     button:SetText( "Close" )
  5498.     button.DoClick = function() window:Close() end
  5499.     button:SetSize( 100, 40 )
  5500.     button:SetPos( (window:GetWide() - button:GetWide()) / 2, window:GetTall() - button:GetTall() - 10 )
  5501.  
  5502.     html:SetSize( window:GetWide() - 20, window:GetTall() - button:GetTall() - 50 )
  5503.     html:SetPos( 10, 30 )
  5504.     if mode == "1" then -- file
  5505.         html:SetHTML( ULib.fileRead( "data/ulx_motd.txt" ) or "" )
  5506.     elseif mode == "2" then -- generator
  5507.         html:SetHTML( ulx.generateMotdHTML() or "" )
  5508.     else -- URL
  5509.         url = string.gsub( url, "%%curmap%%", game.GetMap() )
  5510.         url = string.gsub( url, "%%steamid%%", steamid )
  5511.         html:OpenURL( url )
  5512.     end
  5513. end
  5514.  
  5515. function ulx.rcvMotd( mode_, data )
  5516.     mode = mode_
  5517.     if mode == "1" then -- file
  5518.         ULib.fileWrite( "data/ulx_motd.txt", data )
  5519.     elseif mode == "2" then -- generator
  5520.         ulx.motdSettings = data
  5521.     else -- URL
  5522.         if data:find( "://", 1, true ) then
  5523.             url = data
  5524.         else
  5525.             url = "http://" .. data
  5526.         end
  5527.     end
  5528. end
  5529.  
  5530. local template_header = [[
  5531. <html>
  5532.     <head>
  5533.         <style>
  5534.             body {
  5535.                 padding: 0;
  5536.                 margin: 0;
  5537.                 height: 100%;
  5538.                 font-family: {{style.fonts.regular.family}};
  5539.                 font-size: {{style.fonts.regular.size}};
  5540.                 font-weight: {{style.fonts.regular.weight}};
  5541.                 color: {{style.colors.text_color}};
  5542.                 background-color: {{style.colors.background_color}};
  5543.             }
  5544.             h1 {
  5545.                 font-family: {{style.fonts.server_name.family}};
  5546.                 font-size: {{style.fonts.server_name.size}};
  5547.                 font-weight: {{style.fonts.server_name.weight}};
  5548.             }
  5549.             h2 {
  5550.                 font-family: {{style.fonts.section_title.family}};
  5551.                 font-size: {{style.fonts.section_title.size}};
  5552.                 font-weight: {{style.fonts.section_title.weight}};
  5553.                 color: {{style.colors.section_text_color}};
  5554.             }
  5555.             h3 {
  5556.                 font-family: {{style.fonts.subtitle.family}};
  5557.                 font-size: {{style.fonts.subtitle.size}};
  5558.                 font-weight: {{style.fonts.subtitle.weight}};
  5559.             }
  5560.             p {
  5561.                 padding-left: 20px;
  5562.             }
  5563.             ul, ol {
  5564.                 padding-left: 40px;
  5565.             }
  5566.             .container {
  5567.                 min-height: 100%;
  5568.                 position: relative;
  5569.             }
  5570.             .header, .footer {
  5571.                 width: 100%;
  5572.                 text-align: center;
  5573.                 background-color: {{style.colors.header_color}};
  5574.                 color: {{style.colors.header_text_color}};
  5575.             }
  5576.             .header {
  5577.                 padding: 20px 0;
  5578.                 border-bottom: {{style.borders.border_thickness}} solid {{style.borders.border_color}};
  5579.             }
  5580.             .footer {
  5581.                 position:absolute;
  5582.                 bottom:0;
  5583.                 border-top: {{style.borders.border_thickness}} solid {{style.borders.border_color}};
  5584.                 height: 68px;
  5585.             }
  5586.             .page {
  5587.                 width: 90%;
  5588.                 margin: 0px auto;
  5589.                 padding: 10px;
  5590.                 text-align: left;
  5591.                 padding-bottom: 68px;
  5592.             }
  5593.             .section {
  5594.                 margin-bottom: 32px;
  5595.             }
  5596.         </style>
  5597.     </head>
  5598.     <body>
  5599.         <div class="container">
  5600.             <div class="header">
  5601.                 <h1>%hostname%</h1>
  5602.                 <h3>{{info.description}}</h3>
  5603.             </div>
  5604.             <div class="page">
  5605. ]]
  5606.  
  5607. local template_section = [[
  5608.                 <div class="section">
  5609.                     <h2>%title%</h2>
  5610.                     %content%
  5611.                 </div>
  5612. ]]
  5613.  
  5614. local template_section_p = [[
  5615.                     <p>
  5616.                         %items%
  5617.                     </p>
  5618. ]]
  5619.  
  5620. local template_section_ol = [[
  5621.                     <ol>
  5622.                         %items%
  5623.                     </ol>
  5624. ]]
  5625.  
  5626. local template_section_ul = [[
  5627.                     <ul>
  5628.                         %items%
  5629.                     </ul>
  5630. ]]
  5631.  
  5632. local template_item_li = [[
  5633.                         <li>%content%</li>
  5634. ]]
  5635.  
  5636. local template_item_br = [[
  5637.                         %content%</br>
  5638. ]]
  5639.  
  5640. local template_item_addon = [[
  5641.                         <li><b>%title%</b> by %author%</li>
  5642. ]]
  5643.  
  5644. local template_item_workshop = [[
  5645.                         <li><b>%title%</b> - <a href="http://steamcommunity.com/sharedfiles/filedetails/?id=%workshop_id%">View on Workshop</a></li>
  5646. ]]
  5647.  
  5648. local template_footer = [[
  5649.             </div>
  5650.             <div class="footer">
  5651.                 <h3>Powered by ULX</h3>
  5652.             </div>
  5653.         </div>
  5654.     </body>
  5655. </html>
  5656. ]]
  5657.  
  5658. local template_error = [[
  5659. <html>
  5660.     <head>
  5661.     </head>
  5662.     <body style="background-color: white">
  5663.         <div class="footer">
  5664.             <h3>ULX: MOTD Generator error. Could not parse settings file.</h3>
  5665.         </div>
  5666.     </body>
  5667. </html>
  5668. ]]
  5669.  
  5670. local function escape(str)
  5671.     return (str:gsub("<", "&lt;"):gsub(">", "&gt;")) -- Wrapped in parenthesis so we ignore other return vals
  5672. end
  5673.  
  5674. local function renderItemTemplate(items, template)
  5675.     local output = ""
  5676.     for i=1, #items do
  5677.         output = output .. string.gsub( template, "%%content%%", escape(items[i] or ""))
  5678.     end
  5679.     return output
  5680. end
  5681.  
  5682. local function renderMods()
  5683.     local output = ""
  5684.     for a=1, #ulx.motdSettings.addons do
  5685.         local addon = ulx.motdSettings.addons[a]
  5686.         if addon.workshop_id then
  5687.             local item = string.gsub( template_item_workshop, "%%title%%", escape(addon.title) )
  5688.             output = output .. string.gsub( item, "%%workshop_id%%", escape(addon.workshop_id or "") )
  5689.         else
  5690.             local item = string.gsub( template_item_addon, "%%title%%", escape(addon.title or "") )
  5691.             output = output .. string.gsub( item, "%%author%%", escape(addon.author or "") )
  5692.         end
  5693.     end
  5694.  
  5695.     return output
  5696. end
  5697.  
  5698. function ulx.generateMotdHTML()
  5699.     if ulx.motdSettings == nil or ulx.motdSettings.info == nil then return template_error end
  5700.  
  5701.     local header = string.gsub( template_header, "%%hostname%%", escape(GetHostName() or "") )
  5702.     header = string.gsub( header, "{{(.-)}}", function(a)
  5703.         local success, value = ULib.findVar(a, ulx.motdSettings)
  5704.         return escape( value or "")
  5705.     end )
  5706.  
  5707.     local body = ""
  5708.  
  5709.     for i=1, #ulx.motdSettings.info do
  5710.         local data = ulx.motdSettings.info[i]
  5711.         local content = ""
  5712.  
  5713.         if data.type == "text" then
  5714.             content = string.gsub( template_section_p, "%%items%%", renderItemTemplate(data.contents, template_item_br) )
  5715.  
  5716.         elseif data.type == "ordered_list" then
  5717.             content = string.gsub( template_section_ol, "%%items%%", renderItemTemplate(data.contents, template_item_li) )
  5718.  
  5719.         elseif data.type == "list" then
  5720.             content = string.gsub( template_section_ul, "%%items%%", renderItemTemplate(data.contents, template_item_li) )
  5721.  
  5722.         elseif data.type == "mods" then
  5723.             content = string.gsub( template_section_ul, "%%items%%", renderMods() )
  5724.  
  5725.         elseif data.type == "admins" then
  5726.             local users = {}
  5727.             for g=1, #data.contents do
  5728.                 local group = data.contents[g]
  5729.                 if ulx.motdSettings.admins[group] then
  5730.                     for u=1, #ulx.motdSettings.admins[group] do
  5731.                         table.insert( users, ulx.motdSettings.admins[group][u] )
  5732.                     end
  5733.                 end
  5734.             end
  5735.             table.sort( users )
  5736.             content = string.gsub( template_section_ul, "%%items%%", renderItemTemplate(users, template_item_li) )
  5737.         end
  5738.  
  5739.         local section = string.gsub( template_section, "%%title%%", escape(data.title or "") )
  5740.         body = body .. string.gsub( section, "%%content%%", content )
  5741.     end
  5742.  
  5743.     return string.format( "%s%s%s", header, body, template_footer )
  5744. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement