· 5 years ago · Mar 08, 2020, 09:36 AM
1-- This module holds any type of chatting functions
2CATEGORY_NAME = "Chat"
3
4------------------------------ Psay ------------------------------
5function 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 )
12end
13local psay = ulx.command( CATEGORY_NAME, "ulx psay", ulx.psay, "!p", true )
14psay:addParam{ type=ULib.cmds.PlayerArg, target="!^", ULib.cmds.ignoreCanTarget }
15psay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
16psay:defaultAccess( ULib.ACCESS_ALL )
17psay:help( "Send a private message to target." )
18
19------------------------------ Asay ------------------------------
20local seeasayAccess = "ulx seeasay"
21if 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
23function 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 )
42end
43local asay = ulx.command( CATEGORY_NAME, "ulx asay", ulx.asay, "@", true, true )
44asay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
45asay:defaultAccess( ULib.ACCESS_ALL )
46asay:help( "Send a message to currently connected admins." )
47
48------------------------------ Tsay ------------------------------
49function 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
55end
56local tsay = ulx.command( CATEGORY_NAME, "ulx tsay", ulx.tsay, "@@", true, true )
57tsay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
58tsay:defaultAccess( ULib.ACCESS_ADMIN )
59tsay:help( "Send a message to everyone in the chat box." )
60
61------------------------------ Csay ------------------------------
62function 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
68end
69local csay = ulx.command( CATEGORY_NAME, "ulx csay", ulx.csay, "@@@", true, true )
70csay:addParam{ type=ULib.cmds.StringArg, hint="message", ULib.cmds.takeRestOfLine }
71csay:defaultAccess( ULib.ACCESS_ADMIN )
72csay:help( "Send a message to everyone in the middle of their screen." )
73
74------------------------------ Thetime ------------------------------
75local waittime = 60
76local lasttimeusage = -waittime
77function 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") )
85end
86local thetime = ulx.command( CATEGORY_NAME, "ulx thetime", ulx.thetime, "!thetime" )
87thetime:defaultAccess( ULib.ACCESS_ALL )
88thetime:help( "Shows you the server time." )
89
90
91------------------------------ Adverts ------------------------------
92ulx.adverts = ulx.adverts or {}
93local adverts = ulx.adverts -- For XGUI, too lazy to change all refs
94
95local 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 )
130end
131
132-- Whether or not it's a csay is determined by whether there's a value specified in "len"
133function 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
152end
153
154------------------------------ Gimp ------------------------------
155ulx.gimpSays = ulx.gimpSays or {} -- Holds gimp says
156local gimpSays = ulx.gimpSays -- For XGUI, too lazy to change all refs
157local ID_GIMP = 1
158local ID_MUTE = 2
159
160function ulx.addGimpSay( say )
161 table.insert( gimpSays, say )
162end
163
164function ulx.clearGimpSays()
165 table.Empty( gimpSays )
166end
167
168function 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
184end
185local gimp = ulx.command( CATEGORY_NAME, "ulx gimp", ulx.gimp, "!gimp" )
186gimp:addParam{ type=ULib.cmds.PlayersArg }
187gimp:addParam{ type=ULib.cmds.BoolArg, invisible=true }
188gimp:defaultAccess( ULib.ACCESS_ADMIN )
189gimp:help( "Gimps target(s) so they are unable to chat normally." )
190gimp:setOpposite( "ulx ungimp", {_, _, true}, "!ungimp" )
191
192------------------------------ Mute ------------------------------
193function 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
209end
210local mute = ulx.command( CATEGORY_NAME, "ulx mute", ulx.mute, "!mute" )
211mute:addParam{ type=ULib.cmds.PlayersArg }
212mute:addParam{ type=ULib.cmds.BoolArg, invisible=true }
213mute:defaultAccess( ULib.ACCESS_ADMIN )
214mute:help( "Mutes target(s) so they are unable to chat." )
215mute:setOpposite( "ulx unmute", {_, _, true}, "!unmute" )
216
217if 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 )
226end
227
228------------------------------ Gag ------------------------------
229function 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
242end
243local gag = ulx.command( CATEGORY_NAME, "ulx gag", ulx.gag, "!gag" )
244gag:addParam{ type=ULib.cmds.PlayersArg }
245gag:addParam{ type=ULib.cmds.BoolArg, invisible=true }
246gag:defaultAccess( ULib.ACCESS_ADMIN )
247gag:help( "Gag target(s), disables microphone." )
248gag:setOpposite( "ulx ungag", {_, _, true}, "!ungag" )
249
250local function gagHook( listener, talker )
251 if talker.ulx_gagged then
252 return false
253 end
254end
255hook.Add( "PlayerCanHearPlayersVoice", "ULXGag", gagHook )
256
257-- Anti-spam stuff
258if 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
305end
306
307local 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)
316end
317hook.Add( "PlayerInitialSpawn", "ULXWelcome", showWelcome )
318if 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 )
321end
322local CATEGORY_NAME = "Fun"
323
324------------------------------ Slap ------------------------------
325function 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 )
339end
340
341local slap = ulx.command( CATEGORY_NAME, "ulx slap", ulx.slap, "!slap" )
342slap:addParam{ type=ULib.cmds.PlayersArg }
343slap:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="damage", ULib.cmds.optional, ULib.cmds.round }
344slap:defaultAccess( ULib.ACCESS_ADMIN )
345slap:help( "Slaps target(s) with given damage." )
346
347------------------------------ Whip ------------------------------
348function 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 )
384end
385local whip = ulx.command( CATEGORY_NAME, "ulx whip", ulx.whip, "!whip" )
386whip:addParam{ type=ULib.cmds.PlayersArg }
387whip:addParam{ type=ULib.cmds.NumArg, min=2, max=100, default=10, hint="times", ULib.cmds.optional, ULib.cmds.round }
388whip:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="damage", ULib.cmds.optional, ULib.cmds.round }
389whip:defaultAccess( ULib.ACCESS_ADMIN )
390whip:help( "Slaps target(s) x times with given damage each time." )
391
392------------------------------ Slay ------------------------------
393function 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 )
412end
413local slay = ulx.command( CATEGORY_NAME, "ulx slay", ulx.slay, "!slay" )
414slay:addParam{ type=ULib.cmds.PlayersArg }
415slay:defaultAccess( ULib.ACCESS_ADMIN )
416slay:help( "Slays target(s)." )
417
418------------------------------ Sslay ------------------------------
419function 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 )
442end
443local sslay = ulx.command( CATEGORY_NAME, "ulx sslay", ulx.sslay, "!sslay" )
444sslay:addParam{ type=ULib.cmds.PlayersArg }
445sslay:defaultAccess( ULib.ACCESS_ADMIN )
446sslay:help( "Silently slays target(s)." )
447
448------------------------------ Ignite ------------------------------
449function 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
471end
472local ignite = ulx.command( CATEGORY_NAME, "ulx ignite", ulx.ignite, "!ignite" )
473ignite:addParam{ type=ULib.cmds.PlayersArg }
474ignite:addParam{ type=ULib.cmds.NumArg, min=1, max=300, default=300, hint="seconds", ULib.cmds.optional, ULib.cmds.round }
475ignite:addParam{ type=ULib.cmds.BoolArg, invisible=true }
476ignite:defaultAccess( ULib.ACCESS_ADMIN )
477ignite:help( "Ignites target(s)." )
478ignite:setOpposite( "ulx unignite", {_, _, _, true}, "!unignite" )
479
480local 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
485end
486hook.Add( "PlayerDeath", "ULXCheckFireDeath", checkFireDeath, HOOK_MONITOR_HIGH )
487
488------------------------------ Unigniteall ------------------------------
489function 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" )
506end
507local unigniteall = ulx.command( CATEGORY_NAME, "ulx unigniteall", ulx.unigniteall, "!unigniteall" )
508unigniteall:defaultAccess( ULib.ACCESS_ADMIN )
509unigniteall:help( "Extinguishes all players and all entities." )
510
511------------------------------ Playsound ------------------------------
512function 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 )
523end
524local playsound = ulx.command( CATEGORY_NAME, "ulx playsound", ulx.playsound )
525playsound:addParam{ type=ULib.cmds.StringArg, hint="sound", autocomplete_fn=ulx.soundComplete }
526playsound:defaultAccess( ULib.ACCESS_ADMIN )
527playsound:help( "Plays a sound (relative to sound dir)." )
528
529------------------------------ Freeze ------------------------------
530function 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
566end
567local freeze = ulx.command( CATEGORY_NAME, "ulx freeze", ulx.freeze, "!freeze" )
568freeze:addParam{ type=ULib.cmds.PlayersArg }
569freeze:addParam{ type=ULib.cmds.BoolArg, invisible=true }
570freeze:defaultAccess( ULib.ACCESS_ADMIN )
571freeze:help( "Freezes target(s)." )
572freeze:setOpposite( "ulx unfreeze", {_, _, true}, "!unfreeze" )
573
574------------------------------ God ------------------------------
575function 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
608end
609local god = ulx.command( CATEGORY_NAME, "ulx god", ulx.god, "!god" )
610god:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
611god:addParam{ type=ULib.cmds.BoolArg, invisible=true }
612god:defaultAccess( ULib.ACCESS_ADMIN )
613god:help( "Grants god mode to target(s)." )
614god:setOpposite( "ulx ungod", {_, _, true}, "!ungod" )
615
616------------------------------ Hp ------------------------------
617function 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 )
622end
623local hp = ulx.command( CATEGORY_NAME, "ulx hp", ulx.hp, "!hp" )
624hp:addParam{ type=ULib.cmds.PlayersArg }
625hp:addParam{ type=ULib.cmds.NumArg, min=1, max=2^32/2-1, hint="hp", ULib.cmds.round }
626hp:defaultAccess( ULib.ACCESS_ADMIN )
627hp:help( "Sets the hp for target(s)." )
628
629------------------------------ Armor ------------------------------
630function 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 )
635end
636local armor = ulx.command( CATEGORY_NAME, "ulx armor", ulx.armor, "!armor" )
637armor:addParam{ type=ULib.cmds.PlayersArg }
638armor:addParam{ type=ULib.cmds.NumArg, min=0, max=255, hint="armor", ULib.cmds.round }
639armor:defaultAccess( ULib.ACCESS_ADMIN )
640armor:help( "Sets the armor for target(s)." )
641
642------------------------------ Cloak ------------------------------
643function 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
660end
661local cloak = ulx.command( CATEGORY_NAME, "ulx cloak", ulx.cloak, "!cloak" )
662cloak:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
663cloak:addParam{ type=ULib.cmds.NumArg, min=0, max=255, default=255, hint="amount", ULib.cmds.round, ULib.cmds.optional }
664cloak:addParam{ type=ULib.cmds.BoolArg, invisible=true }
665cloak:defaultAccess( ULib.ACCESS_ADMIN )
666cloak:help( "Cloaks target(s)." )
667cloak:setOpposite( "ulx uncloak", {_, _, _, true}, "!uncloak" )
668
669------------------------------ Blind ------------------------------
670function 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
696end
697local blind = ulx.command( CATEGORY_NAME, "ulx blind", ulx.blind, "!blind" )
698blind:addParam{ type=ULib.cmds.PlayersArg }
699blind:addParam{ type=ULib.cmds.NumArg, min=0, max=255, default=255, hint="amount", ULib.cmds.round, ULib.cmds.optional }
700blind:addParam{ type=ULib.cmds.BoolArg, invisible=true }
701blind:defaultAccess( ULib.ACCESS_ADMIN )
702blind:help( "Blinds target(s)." )
703blind:setOpposite( "ulx unblind", {_, _, _, true}, "!unblind" )
704
705------------------------------ Jail ------------------------------
706local doJail
707local jailableArea
708function 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
739end
740local jail = ulx.command( CATEGORY_NAME, "ulx jail", ulx.jail, "!jail" )
741jail:addParam{ type=ULib.cmds.PlayersArg }
742jail:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="seconds, 0 is forever", ULib.cmds.round, ULib.cmds.optional }
743jail:addParam{ type=ULib.cmds.BoolArg, invisible=true }
744jail:defaultAccess( ULib.ACCESS_ADMIN )
745jail:help( "Jails target(s)." )
746jail:setOpposite( "ulx unjail", {_, _, _, true}, "!unjail" )
747
748------------------------------ Jail TP ------------------------------
749function 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 )
789end
790local jailtp = ulx.command( CATEGORY_NAME, "ulx jailtp", ulx.jailtp, "!jailtp" )
791jailtp:addParam{ type=ULib.cmds.PlayerArg }
792jailtp:addParam{ type=ULib.cmds.NumArg, min=0, default=0, hint="seconds, 0 is forever", ULib.cmds.round, ULib.cmds.optional }
793jailtp:defaultAccess( ULib.ACCESS_ADMIN )
794jailtp:help( "Teleports, then jails target(s)." )
795
796local 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
817end
818
819jailableArea = 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
828end
829
830local mdl1 = Model( "models/props_building_details/Storefront_Template001a_Bars.mdl" )
831local 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}
841doJail = 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 )
928end
929
930local function jailDisconnectedCheck( ply )
931 if ply.jail then
932 ply.jail.unjail()
933 end
934end
935hook.Add( "PlayerDisconnected", "ULXJailDisconnectedCheck", jailDisconnectedCheck, HOOK_MONITOR_HIGH )
936
937local 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
943end
944hook.Add( "PhysgunPickup", "ulxPlayerPickupJailCheck", playerPickup, HOOK_MONITOR_HIGH )
945
946local 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
951end
952hook.Add( "PhysgunDrop", "ulxPlayerDropJailCheck", playerDrop )
953
954------------------------------ Ragdoll ------------------------------
955local 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" )
996end
997
998local 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 )
1024end
1025
1026function 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
1051end
1052local ragdoll = ulx.command( CATEGORY_NAME, "ulx ragdoll", ulx.ragdoll, "!ragdoll" )
1053ragdoll:addParam{ type=ULib.cmds.PlayersArg }
1054ragdoll:addParam{ type=ULib.cmds.BoolArg, invisible=true }
1055ragdoll:defaultAccess( ULib.ACCESS_ADMIN )
1056ragdoll:help( "ragdolls target(s)." )
1057ragdoll:setOpposite( "ulx unragdoll", {_, _, true}, "!unragdoll" )
1058
1059local 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
1068end
1069hook.Add( "PlayerSpawn", "ULXRagdollSpawnCheck", ragdollSpawnCheck )
1070
1071local function ragdollDisconnectedCheck( ply )
1072 if ply.ragdoll then
1073 ply.ragdoll:DisallowDeleting( false )
1074 ply.ragdoll:Remove()
1075 end
1076end
1077hook.Add( "PlayerDisconnected", "ULXRagdollDisconnectedCheck", ragdollDisconnectedCheck, HOOK_MONITOR_HIGH )
1078
1079local 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
1088end
1089hook.Add("PreCleanupMap","ULXRagdollBeforeCleanup", removeRagdollOnCleanup )
1090
1091local 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
1102end
1103hook.Add("PostCleanupMap","ULXRagdollAfterCleanup", createRagdollAfterCleanup )
1104
1105------------------------------ Maul ------------------------------
1106local zombieDeath -- We need these registered up here because functions reference each other.
1107local checkMaulDeath
1108
1109local 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
1126end
1127
1128-- Utility function
1129zombieDeath = 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
1149end
1150
1151-- Another utility for maul
1152local 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
1171end
1172
1173function 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 )
1246end
1247local maul = ulx.command( CATEGORY_NAME, "ulx maul", ulx.maul, "!maul" )
1248maul:addParam{ type=ULib.cmds.PlayersArg }
1249maul:defaultAccess( ULib.ACCESS_SUPERADMIN )
1250maul:help( "Maul target(s)." )
1251
1252checkMaulDeath = 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
1309end
1310hook.Add( "PlayerDeath", "ULXCheckMaulDeath", checkMaulDeath, HOOK_HIGH ) -- Hook it first because we're changing speed. Want others to override us.
1311
1312local function maulDisconnectedCheck( ply )
1313 checkMaulDeath( ply ) -- Just run it through the death function
1314end
1315hook.Add( "PlayerDisconnected", "ULXMaulDisconnectedCheck", maulDisconnectedCheck, HOOK_MONITOR_HIGH )
1316
1317------------------------------ Strip ------------------------------
1318function 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 )
1324end
1325local strip = ulx.command( CATEGORY_NAME, "ulx strip", ulx.stripweapons, "!strip" )
1326strip:addParam{ type=ULib.cmds.PlayersArg }
1327strip:defaultAccess( ULib.ACCESS_ADMIN )
1328strip:help( "Strip weapons from target(s)." )
1329
1330local CATEGORY_NAME = "Menus"
1331
1332if 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
1455end
1456-- This module holds any type of remote execution functions (IE, 'dangerous')
1457local CATEGORY_NAME = "Rcon"
1458
1459function ulx.rcon( calling_ply, command )
1460 ULib.consoleCommand( command .. "\n" )
1461
1462 ulx.fancyLogAdmin( calling_ply, true, "#A ran rcon command: #s", command )
1463end
1464local rcon = ulx.command( CATEGORY_NAME, "ulx rcon", ulx.rcon, "!rcon", true, false, true )
1465rcon:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
1466rcon:defaultAccess( ULib.ACCESS_SUPERADMIN )
1467rcon:help( "Execute command on server console." )
1468
1469function 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 )
1496end
1497local luarun = ulx.command( CATEGORY_NAME, "ulx luarun", ulx.luaRun, nil, false, false, true )
1498luarun:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
1499luarun:defaultAccess( ULib.ACCESS_SUPERADMIN )
1500luarun:help( "Executes lua in server console. (Use '=' for output)" )
1501
1502function 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 )
1511end
1512local exec = ulx.command( CATEGORY_NAME, "ulx exec", ulx.exec, nil, false, false, true )
1513exec:addParam{ type=ULib.cmds.StringArg, hint="file" }
1514exec:defaultAccess( ULib.ACCESS_SUPERADMIN )
1515exec:help( "Execute a file from the cfg directory on the server." )
1516
1517function 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 )
1523end
1524local cexec = ulx.command( CATEGORY_NAME, "ulx cexec", ulx.cexec, "!cexec", false, false, true )
1525cexec:addParam{ type=ULib.cmds.PlayersArg }
1526cexec:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine }
1527cexec:defaultAccess( ULib.ACCESS_SUPERADMIN )
1528cexec:help( "Run a command on console of target(s)." )
1529
1530function 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
1570end
1571local ent = ulx.command( CATEGORY_NAME, "ulx ent", ulx.ent, nil, false, false, true )
1572ent:addParam{ type=ULib.cmds.StringArg, hint="classname" }
1573ent:addParam{ type=ULib.cmds.StringArg, hint="<flag> : <value> |", ULib.cmds.takeRestOfLine, ULib.cmds.optional }
1574ent:defaultAccess( ULib.ACCESS_SUPERADMIN )
1575ent:help( "Spawn an ent, separate flag and value with ':', flag:value pairs with '|'." )
1576CATEGORY_NAME = "Teleport"
1577
1578local 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
1605end
1606local tpGrid = spiralGrid( 24 )
1607
1608-- Utility function for bring, goto, and send
1609local 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
1647end
1648
1649-- Based on code donated by Timmy (https://github.com/Toxsa)
1650function 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
1738end
1739local bring = ulx.command( CATEGORY_NAME, "ulx bring", ulx.bring, "!bring" )
1740bring:addParam{ type=ULib.cmds.PlayersArg, target="!^" }
1741bring:defaultAccess( ULib.ACCESS_ADMIN )
1742bring:help( "Brings target(s) to you." )
1743
1744function 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 )
1787end
1788local goto = ulx.command( CATEGORY_NAME, "ulx goto", ulx.goto, "!goto" )
1789goto:addParam{ type=ULib.cmds.PlayerArg, target="!^", ULib.cmds.ignoreCanTarget }
1790goto:defaultAccess( ULib.ACCESS_ADMIN )
1791goto:help( "Goto target." )
1792
1793function 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 )
1841end
1842local send = ulx.command( CATEGORY_NAME, "ulx send", ulx.send, "!send" )
1843send:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
1844send:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
1845send:defaultAccess( ULib.ACCESS_ADMIN )
1846send:help( "Goto target." )
1847
1848function 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
1892end
1893local teleport = ulx.command( CATEGORY_NAME, "ulx teleport", ulx.teleport, {"!tp", "!teleport"} )
1894teleport:addParam{ type=ULib.cmds.PlayerArg, ULib.cmds.optional }
1895teleport:defaultAccess( ULib.ACCESS_ADMIN )
1896teleport:help( "Teleports target." )
1897
1898function 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 )
1930end
1931local retrn = ulx.command( CATEGORY_NAME, "ulx return", ulx.retrn, "!return" )
1932retrn:addParam{ type=ULib.cmds.PlayerArg, ULib.cmds.optional }
1933retrn:defaultAccess( ULib.ACCESS_ADMIN )
1934retrn:help( "Returns target to last position before a teleport." )
1935local CATEGORY_NAME = "User Management"
1936
1937local 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
1956end
1957
1958ulx.group_names = {}
1959ulx.group_names_no_user = {}
1960local 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
1970end
1971hook.Add( ULib.HOOK_UCLCHANGED, "ULXGroupNamesUpdate", updateNames )
1972updateNames() -- Init
1973
1974function ulx.usermanagementhelp( calling_ply )
1975 if calling_ply:IsValid() then
1976 ULib.clientRPC( calling_ply, "ulx.showUserHelp" )
1977 else
1978 ulx.showUserHelp()
1979 end
1980end
1981local usermanagementhelp = ulx.command( CATEGORY_NAME, "ulx usermanagementhelp", ulx.usermanagementhelp )
1982usermanagementhelp:defaultAccess( ULib.ACCESS_ALL )
1983usermanagementhelp:help( "See the user management help." )
1984
1985function 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 )
1994end
1995local adduser = ulx.command( CATEGORY_NAME, "ulx adduser", ulx.adduser, nil, false, false, true )
1996adduser:addParam{ type=ULib.cmds.PlayerArg }
1997adduser:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
1998adduser:defaultAccess( ULib.ACCESS_SUPERADMIN )
1999adduser:help( "Add a user to specified group." )
2000
2001function 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
2015end
2016local adduserid = ulx.command( CATEGORY_NAME, "ulx adduserid", ulx.adduserid, nil, false, false, true )
2017adduserid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
2018adduserid:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2019adduserid:defaultAccess( ULib.ACCESS_SUPERADMIN )
2020adduserid:help( "Add a user by ID to specified group." )
2021
2022function 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 )
2026end
2027local removeuser = ulx.command( CATEGORY_NAME, "ulx removeuser", ulx.removeuser, nil, false, false, true )
2028removeuser:addParam{ type=ULib.cmds.PlayerArg }
2029removeuser:defaultAccess( ULib.ACCESS_SUPERADMIN )
2030removeuser:help( "Permanently removes a user's access." )
2031
2032function 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
2052end
2053local removeuserid = ulx.command( CATEGORY_NAME, "ulx removeuserid", ulx.removeuserid, nil, false, false, true )
2054removeuserid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
2055removeuserid:defaultAccess( ULib.ACCESS_SUPERADMIN )
2056removeuserid:help( "Permanently removes a user's access by ID." )
2057
2058function 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
2081end
2082local userallow = ulx.command( CATEGORY_NAME, "ulx userallow", ulx.userallow, nil, false, false, true )
2083userallow:addParam{ type=ULib.cmds.PlayerArg }
2084userallow:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2085userallow:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
2086userallow:defaultAccess( ULib.ACCESS_SUPERADMIN )
2087userallow:help( "Add to a user's access." )
2088
2089function 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
2119end
2120local userallowid = ulx.command( CATEGORY_NAME, "ulx userallowid", ulx.userallowid, nil, false, false, true )
2121userallowid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
2122userallowid:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2123userallowid:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
2124userallowid:defaultAccess( ULib.ACCESS_SUPERADMIN )
2125userallowid:help( "Add to a user's access." )
2126
2127function 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
2146end
2147local userdeny = ulx.command( CATEGORY_NAME, "ulx userdeny", ulx.userdeny, nil, false, false, true )
2148userdeny:addParam{ type=ULib.cmds.PlayerArg }
2149userdeny:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2150userdeny:addParam{ type=ULib.cmds.BoolArg, hint="remove explicit allow or deny instead of outright denying", ULib.cmds.optional }
2151userdeny:defaultAccess( ULib.ACCESS_SUPERADMIN )
2152userdeny:help( "Remove from a user's access." )
2153
2154function 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
2184end
2185local userdenyid = ulx.command( CATEGORY_NAME, "ulx userdenyid", ulx.userdenyid, nil, false, false, true )
2186userdenyid:addParam{ type=ULib.cmds.StringArg, hint="SteamID, IP, or UniqueID" }
2187userdenyid:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2188userdenyid:addParam{ type=ULib.cmds.BoolArg, hint="remove explicit allow or deny instead of outright denying", ULib.cmds.optional }
2189userdenyid:defaultAccess( ULib.ACCESS_SUPERADMIN )
2190userdenyid:help( "Remove from a user's access." )
2191
2192function 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 )
2205end
2206local addgroup = ulx.command( CATEGORY_NAME, "ulx addgroup", ulx.addgroup, nil, false, false, true )
2207addgroup:addParam{ type=ULib.cmds.StringArg, hint="group" }
2208addgroup: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 }
2209addgroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
2210addgroup:help( "Create a new group with optional inheritance." )
2211
2212function 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 )
2220end
2221local renamegroup = ulx.command( CATEGORY_NAME, "ulx renamegroup", ulx.renamegroup, nil, false, false, true )
2222renamegroup:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="current group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2223renamegroup:addParam{ type=ULib.cmds.StringArg, hint="new group" }
2224renamegroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
2225renamegroup:help( "Renames a group." )
2226
2227function 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
2235end
2236local setgroupcantarget = ulx.command( CATEGORY_NAME, "ulx setgroupcantarget", ulx.setGroupCanTarget, nil, false, false, true )
2237setgroupcantarget:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2238setgroupcantarget:addParam{ type=ULib.cmds.StringArg, hint="target string", ULib.cmds.optional }
2239setgroupcantarget:defaultAccess( ULib.ACCESS_SUPERADMIN )
2240setgroupcantarget:help( "Sets what a group is allowed to target" )
2241
2242function ulx.removegroup( calling_ply, group_name )
2243 ULib.ucl.removeGroup( group_name )
2244 ulx.fancyLogAdmin( calling_ply, "#A removed group #s", group_name )
2245end
2246local removegroup = ulx.command( CATEGORY_NAME, "ulx removegroup", ulx.removegroup, nil, false, false, true )
2247removegroup:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names_no_user, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2248removegroup:defaultAccess( ULib.ACCESS_SUPERADMIN )
2249removegroup:help( "Removes a group. USE WITH CAUTION." )
2250
2251function 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
2271end
2272local groupallow = ulx.command( CATEGORY_NAME, "ulx groupallow", ulx.groupallow, nil, false, false, true )
2273groupallow:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2274groupallow:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2275groupallow:addParam{ type=ULib.cmds.StringArg, hint="access tag", ULib.cmds.optional }
2276groupallow:defaultAccess( ULib.ACCESS_SUPERADMIN )
2277groupallow:help( "Add to a group's access." )
2278
2279function 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
2293end
2294local groupdeny = ulx.command( CATEGORY_NAME, "ulx groupdeny", ulx.groupdeny, nil, false, false, true )
2295groupdeny:addParam{ type=ULib.cmds.StringArg, completes=ulx.group_names, hint="group", error="invalid group \"%s\" specified", ULib.cmds.restrictToCompletes }
2296groupdeny:addParam{ type=ULib.cmds.StringArg, hint="command" } -- TODO, add completes for this
2297groupdeny:defaultAccess( ULib.ACCESS_SUPERADMIN )
2298groupdeny:help( "Remove from a group's access." )
2299
2300local help = [[
2301General User Management Concepts:
2302User access is driven by ULib's Ulysses Control List (UCL). This list contains users and groups
2303which in turn contains lists of allowed and denied accesses. The allow and deny lists contain
2304access strings like "ulx slap" or "ulx phygunplayer" to show what a user and/or group does and does
2305not have access to. If a user has "ulx slap" in their user allow list or in the allow list of one
2306of the groups they belong to, they have access to slap. If a user has "ulx slap" in their user deny
2307list they are DENIED the command, even if they have the command in one of their group's allow
2308lists. In this way, deny takes precedence over allow.
2309
2310ULib supports immunity by being able to specify what various users and groups are allowed to
2311target. This is often used to make it so lower admins cannot target higher admins. EG, by default
2312admins can't target superadmins, but superadmins can still target admins.
2313
2314
2315More Advanced Concepts:
2316Groups have inheritance. You can specify what group they inherit from in the addgroup command. If a
2317user is in a group that has inheritance, UCL will check all groups connected in the inheritance
2318chain. Note that groups do not support deny lists for the sake of simplicity. If you feel that a
2319group needs to be denied something, you should split your groups up instead.
2320
2321The "user" group applies to everyone who does not otherwise belong in a group. You can use
2322groupallow on this group just like any other, just remember that everyone is being allowed access.
2323
2324ULib supports an advanced, highly configurable permission system by using "access tags". Access
2325tags specify what a user is allowed to pass as arguments to a command. For example, you can make it
2326so that admins are only allowed to slay users with "killme" somewhere in their name, or you can
2327give everyone access to the "ulx teleport" command, but only allow them to teleport themselves.
2328
2329Examples of using access tags are given below in the userallow and groupallow commands. The format
2330for access tags is as follows. Each argument that is passed to the command can be limited by the
2331access tag. Each argument being limited must be listed in the same order as in the command,
2332separated by spaces. If you don't want to limit an argument, use a star ("*"). EG, to limit "ulx
2333slap" damage to 0 through 10, but still allow it to be used on anyone, use the tag "* 0:10".
2334
2335User Management Commands:
2336ulx adduser <user> <group> - Add the specified CONNECTED player to the specified group.
2337The group MUST exist for this command to succeed. Use operator, admin, superadmin, or see ulx
2338addgroup. You can only specify one group. See above for explanation on immunity.
2339Ex 1. ulx adduser "Someguy" superadmin -- This will add the connected "Someguy" as a superadmin
2340Ex 2. ulx adduser "Dood" monkey -- This will add the connected "Dood" to the group monkey
2341 on the condition that the group exists
2342
2343ulx removeuser <user> - Remove the specified connected player from the permanent access list.
2344Ex 1. ulx removeuser "Foo bar" -- This removes the user "Foo bar"
2345
2346ulx userallow <user> <access> [<access tag>] - Puts the access on the USER'S ALLOW list, with
2347 optional access tag (see above)
2348See above for explanation of allow list vs. deny list, as well as how access strings/tags work.
2349Ex 1. ulx userallow "Pi" "ulx slap" -- This grants the user access to "ulx slap"
2350Ex 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
2353ulx 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
2357ulx addgroup <group> [<inherits from>] - Creates a group, optionally inheriting from the specified
2358 group. See above for explanation on inheritance.
2359
2360ulx 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
2364ulx renamegroup <current group> <new group> - Renames a group
2365
2366ulx setgroupcantarget <group> [<target string>] - Limits what users a group can target. Pass no
2367 argument to clear the restriction.
2368Ex 1. ulx setgroupcantarget user !%admin - Guests cannot target admins or above
2369Ex 2. ulx setgroupcantarget admin !^ - Admins cannot target themselves
2370
2371ulx groupallow <group> <access> [<access tag>] - Puts the access on the group's allow list. See
2372 above on how access strings/tags work.
2373
2374ulx groupdeny <group> <access> - Removes the group's access.
2375
2376
2377]]
2378
2379function ulx.showUserHelp()
2380 local lines = ULib.explode( "\n", help )
2381 for _, line in ipairs( lines ) do
2382 Msg( line .. "\n" )
2383 end
2384end
2385local CATEGORY_NAME = "Utility"
2386
2387------------------------------ Who ------------------------------
2388function 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
2415end
2416local who = ulx.command( CATEGORY_NAME, "ulx who", ulx.who )
2417who:addParam{ type=ULib.cmds.StringArg, hint="steamid", ULib.cmds.optional }
2418who:defaultAccess( ULib.ACCESS_ALL )
2419who:help( "See information about currently online users." )
2420
2421------------------------------ Version ------------------------------
2422function ulx.versionCmd( calling_ply )
2423 ULib.tsay( calling_ply, "ULib " .. ULib.pluginVersionStr("ULib"), true )
2424 ULib.tsay( calling_ply, "ULX " .. ULib.pluginVersionStr("ULX"), true )
2425end
2426local version = ulx.command( CATEGORY_NAME, "ulx version", ulx.versionCmd, "!version" )
2427version:defaultAccess( ULib.ACCESS_ALL )
2428version:help( "See version information." )
2429
2430------------------------------ Map ------------------------------
2431function 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" )
2441end
2442local map = ulx.command( CATEGORY_NAME, "ulx map", ulx.map, "!map" )
2443map:addParam{ type=ULib.cmds.StringArg, completes=ulx.maps, hint="map", error="invalid map \"%s\" specified", ULib.cmds.restrictToCompletes }
2444map:addParam{ type=ULib.cmds.StringArg, completes=ulx.gamemodes, hint="gamemode", error="invalid gamemode \"%s\" specified", ULib.cmds.restrictToCompletes, ULib.cmds.optional }
2445map:defaultAccess( ULib.ACCESS_ADMIN )
2446map:help( "Changes map and gamemode." )
2447
2448function 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 )
2462end
2463local kick = ulx.command( CATEGORY_NAME, "ulx kick", ulx.kick, "!kick" )
2464kick:addParam{ type=ULib.cmds.PlayerArg }
2465kick:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
2466kick:defaultAccess( ULib.ACCESS_ADMIN )
2467kick:help( "Kicks target." )
2468
2469------------------------------ Ban ------------------------------
2470function 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 )
2483end
2484local ban = ulx.command( CATEGORY_NAME, "ulx ban", ulx.ban, "!ban", false, false, true )
2485ban:addParam{ type=ULib.cmds.PlayerArg }
2486ban:addParam{ type=ULib.cmds.NumArg, hint="minutes, 0 for perma", ULib.cmds.optional, ULib.cmds.allowTimeString, min=0 }
2487ban:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
2488ban:defaultAccess( ULib.ACCESS_ADMIN )
2489ban:help( "Bans target." )
2490
2491------------------------------ BanID ------------------------------
2492function 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 )
2526end
2527local banid = ulx.command( CATEGORY_NAME, "ulx banid", ulx.banid, nil, false, false, true )
2528banid:addParam{ type=ULib.cmds.StringArg, hint="steamid" }
2529banid:addParam{ type=ULib.cmds.NumArg, hint="minutes, 0 for perma", ULib.cmds.optional, ULib.cmds.allowTimeString, min=0 }
2530banid:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
2531banid:defaultAccess( ULib.ACCESS_SUPERADMIN )
2532banid:help( "Bans steamid." )
2533
2534function 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
2549end
2550local unban = ulx.command( CATEGORY_NAME, "ulx unban", ulx.unban, nil, false, false, true )
2551unban:addParam{ type=ULib.cmds.StringArg, hint="steamid" }
2552unban:defaultAccess( ULib.ACCESS_ADMIN )
2553unban:help( "Unbans steamid." )
2554
2555------------------------------ Noclip ------------------------------
2556function 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
2580end
2581local noclip = ulx.command( CATEGORY_NAME, "ulx noclip", ulx.noclip, "!noclip" )
2582noclip:addParam{ type=ULib.cmds.PlayersArg, ULib.cmds.optional }
2583noclip:defaultAccess( ULib.ACCESS_ADMIN )
2584noclip:help( "Toggles noclip on target(s)." )
2585
2586function 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 )
2656end
2657local spectate = ulx.command( CATEGORY_NAME, "ulx spectate", ulx.spectate, "!spectate", true )
2658spectate:addParam{ type=ULib.cmds.PlayerArg, target="!^" }
2659spectate:defaultAccess( ULib.ACCESS_ADMIN )
2660spectate:help( "Spectate target." )
2661
2662function 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
2673end
2674
2675function 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" )
2755end
2756local debuginfo = ulx.command( CATEGORY_NAME, "ulx debuginfo", ulx.debuginfo )
2757debuginfo:help( "Dump some debug information." )
2758
2759function 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" )
2793end
2794local resettodefaults = ulx.command( CATEGORY_NAME, "ulx resettodefaults", ulx.resettodefaults )
2795resettodefaults:addParam{ type=ULib.cmds.StringArg, ULib.cmds.optional }
2796resettodefaults:help( "Resets ALL ULX and ULib configuration!" )
2797
2798if 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 )
2834end
2835
2836--------------------
2837-- Hooks --
2838--------------------
2839-- This cvar also exists in DarkRP (thanks, FPtje)
2840local cl_cvar_pickup = "cl_pickupplayers"
2841if CLIENT then CreateClientConVar( cl_cvar_pickup, "1", true, true ) end
2842local 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
2855end
2856hook.Add( "PhysgunPickup", "ulxPlayerPickup", playerPickup, HOOK_HIGH ) -- Allow admins to move players. Call before the prop protection hook.
2857if SERVER then ULib.ucl.registerAccess( "ulx physgunplayer", ULib.ACCESS_ADMIN, "Ability to physgun other players", "Other" ) end
2858
2859local function playerDrop( ply, ent )
2860 if ent:GetClass() == "player" then
2861 ent:SetMoveType( MOVETYPE_WALK )
2862 end
2863end
2864hook.Add( "PhysgunDrop", "ulxPlayerDrop", playerDrop )
2865
2866local CATEGORY_NAME = "Voting"
2867
2868---------------
2869--Public vote--
2870---------------
2871if SERVER then ulx.convar( "voteEcho", "0", _, ULib.ACCESS_SUPERADMIN ) end -- Echo votes?
2872
2873-- First, our helper function to make voting so much easier!
2874function 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
2909end
2910
2911function 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
2946end
2947if SERVER then concommand.Add( "ulx_vote", ulx.voteCallback ) end
2948
2949function 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
2961end
2962-- End our helper functions
2963
2964
2965
2966
2967
2968local 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" )
2988end
2989
2990function 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 )
2998end
2999local vote = ulx.command( CATEGORY_NAME, "ulx vote", ulx.vote, "!vote" )
3000vote:addParam{ type=ULib.cmds.StringArg, hint="title" }
3001vote:addParam{ type=ULib.cmds.StringArg, hint="options", ULib.cmds.takeRestOfLine, repeat_min=2, repeat_max=10 }
3002vote:defaultAccess( ULib.ACCESS_ADMIN )
3003vote:help( "Starts a public vote." )
3004
3005-- Stop a vote in progress
3006function 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." )
3014end
3015local stopvote = ulx.command( CATEGORY_NAME, "ulx stopvote", ulx.stopVote, "!stopvote" )
3016stopvote:defaultAccess( ULib.ACCESS_SUPERADMIN )
3017stopvote:help( "Stops a vote in progress." )
3018
3019local 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
3032end
3033
3034local 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
3073end
3074
3075function 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
3097end
3098local votemap2 = ulx.command( CATEGORY_NAME, "ulx votemap2", ulx.votemap2, "!votemap2" )
3099votemap2: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 }
3100votemap2:defaultAccess( ULib.ACCESS_ADMIN )
3101votemap2:help( "Starts a public map vote." )
3102if SERVER then ulx.convar( "votemap2Successratio", "0.5", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a votemap2 to succeed
3103if SERVER then ulx.convar( "votemap2Minvotes", "3", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for votemap2
3104
3105
3106
3107local 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
3124end
3125
3126local 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
3157end
3158
3159function 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
3181end
3182local votekick = ulx.command( CATEGORY_NAME, "ulx votekick", ulx.votekick, "!votekick" )
3183votekick:addParam{ type=ULib.cmds.PlayerArg }
3184votekick:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
3185votekick:defaultAccess( ULib.ACCESS_ADMIN )
3186votekick:help( "Starts a public kick vote against target." )
3187if SERVER then ulx.convar( "votekickSuccessratio", "0.6", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a votekick to succeed
3188if SERVER then ulx.convar( "votekickMinvotes", "2", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for votekick
3189
3190
3191
3192local 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
3205end
3206
3207local 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" )
3237end
3238
3239function 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
3261end
3262local voteban = ulx.command( CATEGORY_NAME, "ulx voteban", ulx.voteban, "!voteban" )
3263voteban:addParam{ type=ULib.cmds.PlayerArg }
3264voteban:addParam{ type=ULib.cmds.NumArg, min=0, default=1440, hint="minutes", ULib.cmds.allowTimeString, ULib.cmds.optional }
3265voteban:addParam{ type=ULib.cmds.StringArg, hint="reason", ULib.cmds.optional, ULib.cmds.takeRestOfLine, completes=ulx.common_kick_reasons }
3266voteban:defaultAccess( ULib.ACCESS_ADMIN )
3267voteban:help( "Starts a public ban vote against target." )
3268if SERVER then ulx.convar( "votebanSuccessratio", "0.7", _, ULib.ACCESS_ADMIN ) end -- The ratio needed for a voteban to succeed
3269if SERVER then ulx.convar( "votebanMinvotes", "3", _, ULib.ACCESS_ADMIN ) end -- Minimum votes needed for voteban
3270
3271-- Our regular votemap command
3272local votemap = ulx.command( CATEGORY_NAME, "ulx votemap", ulx.votemap, "!votemap" )
3273votemap:addParam{ type=ULib.cmds.StringArg, completes=ulx.votemaps, hint="map", ULib.cmds.takeRestOfLine, ULib.cmds.optional }
3274votemap:defaultAccess( ULib.ACCESS_ALL )
3275votemap:help( "Vote for a map, no args lists available maps." )
3276
3277-- Our veto command
3278local veto = ulx.command( CATEGORY_NAME, "ulx veto", ulx.votemapVeto, "!veto" )
3279veto:defaultAccess( ULib.ACCESS_ADMIN )
3280veto: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
3287xlib = {}
3288
3289function 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
3346end
3347
3348function 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
3376end
3377
3378function 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
3393end
3394
3395function 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
3426end
3427
3428function 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
3439end
3440
3441function 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
3457end
3458
3459function 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
3508end
3509
3510function 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
3517end
3518
3519function 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
3560end
3561
3562function 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
3570end
3571
3572function 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
3579end
3580
3581function 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
3592end
3593
3594function 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
3721end
3722
3723function 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
3738end
3739
3740function 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
3767end
3768
3769--Thanks to Megiddo for this code! :D
3770function 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
3795end
3796
3797function 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
3806end
3807
3808function 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
3812end
3813
3814function 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
3979end
3980
3981-----------------------------------------
3982--A stripped-down customized DPanel allowing for textbox input!
3983-----------------------------------------
3984local PANEL = {}
3985AccessorFunc( PANEL, "m_bPaintBackground", "PaintBackground" )
3986Derma_Hook( PANEL, "Paint", "Paint", "Panel" )
3987Derma_Hook( PANEL, "ApplySchemeSettings", "Scheme", "Panel" )
3988
3989function PANEL:Init()
3990 self:SetPaintBackground( true )
3991end
3992
3993derma.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-----------------------------------------
4002local PANEL = {}
4003function 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 ) )
4151end
4152
4153function 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 )
4210end
4211
4212function 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 )
4220end
4221
4222function 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 )
4227end
4228
4229function 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() )
4234end
4235
4236function 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 )
4251end
4252
4253function 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() )
4264end
4265
4266function PANEL:SetColorAlpha( alpha )
4267 if ( self.showAlpha ) then
4268 alpha = alpha or 0
4269 self.txtA:SetValue(alpha)
4270 end
4271end
4272
4273function 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() )
4281end
4282
4283function 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
4291end
4292
4293function PANEL:PerformLayout()
4294 self:SetColor( Color( self.txtR:GetValue(), self.txtG:GetValue(), self.txtB:GetValue(), self.showAlpha and self.txtA:GetValue() ) )
4295end
4296
4297function PANEL:OnChangeImmediate( color )
4298 --For override
4299end
4300
4301function PANEL:OnChange( color )
4302 --For override
4303end
4304
4305vgui.Register( "xlibColorPanel", PANEL, "DPanel" )
4306
4307
4308-- Create font for Ban Message preview to match the font used in the actual banned/disconnect dialog.
4309surface.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.
4323local xlibAnimation = {}
4324xlibAnimation.__index = xlibAnimation
4325
4326function 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
4333end
4334
4335xlib.animTypes = {}
4336xlib.registerAnimType = function( name, runFunc, startFunc, endFunc )
4337 xlib.animTypes[name] = xlib.anim( runFunc, startFunc, endFunc )
4338end
4339
4340function 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
4352end
4353
4354function 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()
4361end
4362
4363function 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
4371end
4372
4373--Animation Ticker
4374xlib.activeAnims = {}
4375xlib.animRun = function()
4376 for _, v in ipairs( xlib.activeAnims ) do
4377 v.Run( v )
4378 end
4379end
4380hook.Add( "XLIBDoAnimation", "xlib_runAnims", xlib.animRun )
4381
4382-------------------------
4383--Animation chain manager
4384-------------------------
4385xlib.animQueue = {}
4386xlib.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)
4390xlib.animStep = 0
4391
4392--Call this to begin the animation chain
4393xlib.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()
4401end
4402
4403xlib.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
4407end
4408
4409xlib.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
4423end
4424
4425xlib.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
4441end
4442
4443-------------------------
4444--Default Animation Types
4445-------------------------
4446--Slide animation
4447local 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) )
4450end
4451
4452local 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
4457end
4458
4459local 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
4464end
4465xlib.registerAnimType( "pnlSlide", slideAnim_run, slideAnim_start, slideAnim_end )
4466
4467--Fade animation
4468local 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
4471end
4472
4473local 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
4476end
4477
4478local function fadeAnim_end( data )
4479 if data.panelOut then data.panelOut:SetVisible( false ) end
4480 if data.panelIn then data.panelIn:SetAlpha( 255 ) end
4481end
4482xlib.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
4487function 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
4890end
4891
4892--XGUI: A GUI for ULX -- by Stickly Man!
4893xgui = xgui or {}
4894
4895--Make a spot for modules to store data and hooks
4896xgui.data = xgui.data or {}
4897xgui.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.
4899function 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
4904end
4905
4906--Set up various hooks modules can "hook" into.
4907function 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
4918end
4919
4920--Set up tables and functions for creating and storing modules
4921xgui.modules = xgui.modules or {}
4922
4923xgui.modules.tab = xgui.modules.tab or {}
4924function 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
4937end
4938
4939xgui.modules.setting = xgui.modules.setting or {}
4940function 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
4952end
4953
4954xgui.modules.submodule = xgui.modules.submodule or {}
4955function 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
4966end
4967--Set up a spot to store entries for autocomplete.
4968xgui.tabcompletes = xgui.tabcompletes or {}
4969xgui.ulxmenucompletes = xgui.ulxmenucompletes or {}
4970
4971
4972--Set up XGUI clientside settings, load settings from file if it exists
4973xgui.settings = xgui.settings or {}
4974if 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 )
4978end
4979--Set default settings if they didn't get loaded
4980if not xgui.settings.moduleOrder then xgui.settings.moduleOrder = { "Cmds", "Groups", "Maps", "Settings", "Bans" } end
4981if not xgui.settings.settingOrder then xgui.settings.settingOrder = { "Sandbox", "Server", "Client" } end
4982if not xgui.settings.animTime then xgui.settings.animTime = 0.22 else xgui.settings.animTime = tonumber( xgui.settings.animTime ) end
4983if not xgui.settings.infoColor then
4984 --Default color
4985 xgui.settings.infoColor = Color( 100, 255, 255, 128 )
4986else
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)
4989end
4990if not xgui.settings.showLoadMsgs then xgui.settings.showLoadMsgs = true else xgui.settings.showLoadMsgs = ULib.toBool( xgui.settings.showLoadMsgs ) end
4991if not xgui.settings.skin then xgui.settings.skin = "Default" end
4992if not xgui.settings.xguipos then xgui.settings.xguipos = { pos=5, xoff=0, yoff=0 } end
4993if not xgui.settings.animIntype then xgui.settings.animIntype = 1 end
4994if not xgui.settings.animOuttype then xgui.settings.animOuttype = 1 end
4995
4996
4997function 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()
5081end
5082hook.Add( ULib.HOOK_LOCALPLAYERREADY, "InitXGUI", xgui.init, HOOK_MONITOR_LOW )
5083
5084function 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 )
5091end
5092
5093function 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
5100end
5101
5102function 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
5189end
5190
5191function 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
5235end
5236
5237function 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" )
5303end
5304
5305function 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" )
5316end
5317
5318function 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
5324end
5325
5326--New XGUI Data stuff
5327function 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
5338end
5339
5340function 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
5383end
5384
5385function 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
5397end
5398
5399function 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
5410end
5411
5412--If the player's group is changed, reprocess the XGUI modules for permissions, and request for extra data if needed
5413function 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
5422end
5423hook.Add( "UCLAuthed", "XGUI_PermissionsChanged", xgui.PermissionsChanged )
5424
5425function 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
5435end
5436
5437function 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
5443end
5444
5445function xgui.tab_completes()
5446 return xgui.tabcompletes
5447end
5448
5449function xgui.ulxmenu_tab_completes()
5450 return xgui.ulxmenucompletes
5451end
5452
5453ULib.cmds.addCommandClient( "xgui", xgui.cmd_base )
5454ULib.cmds.addCommandClient( "xgui show", function( ply, cmd, args ) xgui.show( args ) end, xgui.tab_completes )
5455ULib.cmds.addCommandClient( "xgui hide", xgui.hide )
5456ULib.cmds.addCommandClient( "xgui toggle", function() xgui.toggle() end )
5457
5458--local ulxmenu = ulx.command( CATEGORY_NAME, "ulx menu", ulx.menu, "!menu" )
5459ULib.cmds.addCommandClient( "ulx menu", function( ply, cmd, args ) xgui.toggle( args ) end, xgui.ulxmenu_tab_completes )
5460
5461ulx.teams = ulx.teams or {}
5462
5463function 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
5470end
5471
5472ulx.motdmenu_exists = true
5473
5474local mode
5475local url
5476
5477function 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
5513end
5514
5515function 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
5528end
5529
5530local 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
5607local template_section = [[
5608 <div class="section">
5609 <h2>%title%</h2>
5610 %content%
5611 </div>
5612]]
5613
5614local template_section_p = [[
5615 <p>
5616 %items%
5617 </p>
5618]]
5619
5620local template_section_ol = [[
5621 <ol>
5622 %items%
5623 </ol>
5624]]
5625
5626local template_section_ul = [[
5627 <ul>
5628 %items%
5629 </ul>
5630]]
5631
5632local template_item_li = [[
5633 <li>%content%</li>
5634]]
5635
5636local template_item_br = [[
5637 %content%</br>
5638]]
5639
5640local template_item_addon = [[
5641 <li><b>%title%</b> by %author%</li>
5642]]
5643
5644local 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
5648local template_footer = [[
5649 </div>
5650 <div class="footer">
5651 <h3>Powered by ULX</h3>
5652 </div>
5653 </div>
5654 </body>
5655</html>
5656]]
5657
5658local 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
5670local function escape(str)
5671 return (str:gsub("<", "<"):gsub(">", ">")) -- Wrapped in parenthesis so we ignore other return vals
5672end
5673
5674local 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
5680end
5681
5682local 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
5696end
5697
5698function 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 )
5744end