This commit is contained in:
Nayruden 2015-11-07 16:56:00 -05:00
parent 27cd8afbf9
commit 5a393ca6fd
3 changed files with 631 additions and 584 deletions

View File

@ -49,6 +49,7 @@ v2.60 - *(00/00/00)*
* [ADD] ULib.ucl.getUserInfoFromID for getting user info from an ID.
* [ADD] CAMI support.
* [ADD] "noMount" parameter to file-related APIs.
* [ADD] ULibGetUser(s)CustomKeyword hooks (Thanks, LuaTenshi).
* [FIX] The usual random slew of Garry-breakages (Thanks, Fuzzik).
* [FIX] An assumption regarding player authentication that led to a player's group being reset to user sometimes.
* [FIX] Garry API change for ULib.findinDir (Thanks, ascentechit).

View File

@ -1,247 +1,286 @@
--[[
Title: Defines
Holds some defines used on both client and server.
]]
ULib = ULib or {}
ULib.VERSION = 2.60
ULib.ACCESS_ALL = "user"
ULib.ACCESS_OPERATOR = "operator"
ULib.ACCESS_ADMIN = "admin"
ULib.ACCESS_SUPERADMIN = "superadmin"
ULib.DEFAULT_ACCESS = ULib.ACCESS_ALL
ULib.DEFAULT_TSAY_COLOR = Color( 151, 211, 255 ) -- Found by using MS Paint
--[[
Section: Umsg Helpers
These are ids for the ULib umsg functions, so the client knows what they're getting.
]]
ULib.TYPE_ANGLE = 1
ULib.TYPE_BOOLEAN = 2
ULib.TYPE_CHAR = 3
ULib.TYPE_ENTITY = 4
ULib.TYPE_FLOAT = 5
ULib.TYPE_LONG = 6
ULib.TYPE_SHORT = 7
ULib.TYPE_STRING = 8
ULib.TYPE_VECTOR = 9
-- These following aren't actually datatypes, we handle them ourselves
ULib.TYPE_TABLE_BEGIN = 10
ULib.TYPE_TABLE_END = 11
ULib.TYPE_NIL = 12
ULib.RPC_UMSG_NAME = "URPC"
ULib.TYPE_SIZE = {
[ULib.TYPE_ANGLE] = 12, -- 3 floats
[ULib.TYPE_BOOLEAN] = 1,
[ULib.TYPE_CHAR] = 1,
[ULib.TYPE_ENTITY] = 4, -- Found through trial and error
[ULib.TYPE_FLOAT] = 4,
[ULib.TYPE_LONG] = 4,
[ULib.TYPE_SHORT] = 2,
[ULib.TYPE_VECTOR] = 12, -- 3 floats
[ULib.TYPE_NIL] = 0, -- Not technically a type but we handle it anyways
}
ULib.MAX_UMSG_BYTES = 255
--[[
Section: Hooks
These are the hooks that ULib has created that other modders are free to make use of.
]]
--[[
Hook: UCLAuthed
Called *on both server and client* when a player has been (re)authenticated by UCL. Called for ALL players, regardless of access.
Parameters passed to callback:
ply - The player that got (re)authenticated.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_UCLAUTH = "UCLAuthed"
--[[
Hook: UCLChanged
Called *on both server and client* when anything in ULib.ucl.users, ULib.ucl.authed, or ULib.ucl.groups changes. No parameters are passed to callbacks.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_UCLCHANGED = "UCLChanged"
--[[
Hook: ULibReplicatedCvarChanged
Called *on both client and server* when a replicated cvar changes or is created.
Parameters passed to callback:
sv_cvar - The name of the server-side cvar.
cl_cvar - The name of the client-side cvar.
ply - The player changing the cvar or nil on initial value.
old_value - The previous value of the cvar, nil if this call is to set the initial value.
new_value - The new value of the cvar.
Revisions:
v2.40 - Initial
v2.50 - Removed nil on client side restriction.
]]
ULib.HOOK_REPCVARCHANGED = "ULibReplicatedCvarChanged"
--[[
Hook: ULibLocalPlayerReady
Called *on both client and server* when a player entity is created. (can now run commands). Only works for local
player on the client side.
Parameters passed to callback:
ply - The player that's ready (local player on client side).
Revisions:
v2.40 - Initial
]]
ULib.HOOK_LOCALPLAYERREADY = "ULibLocalPlayerReady"
--[[
Hook: ULibCommandCalled
Called *on server* whenever a ULib command is run, return false to override and not allow, true to stop executing callbacks and allow.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
args - The table of args for the command.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_COMMAND_CALLED = "ULibCommandCalled"
--[[
Hook: ULibPlayerTarget
Called whenever one player is about to target another player. Called *BEFORE* any other validation
takes place. Return false and error message to disallow target completely, return true to
override any other validation logic and allow the target to take place, return a player to force
the target to be the specified player.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
target - The proposed target of the command before any other validation logic takes place.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_TARGET = "ULibPlayerTarget"
--[[
Hook: ULibPlayerTargets
Called whenever one player is about to target another set of players. Called *BEFORE* any other validation
takes place. Return false and error message to disallow target completely, return true to
override any other validation logic and allow the target to take place, return a table of players to force
the targets to be the specified players.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
targets - The proposed targets of the command before any other validation logic takes place.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_TARGETS = "ULibPlayerTargets" -- Exactly the same as the above but used when the player is using a command that can target multiple players.
--[[
Hook: ULibPostTranslatedCommand
*Server hook*. Called after a translated command (ULib.cmds.TranslatedCommand) has been successfully
verified. This hook directly follows the callback for the command itself.
Parameters passed to callback:
ply - The player that executed the command.
commandName - The command that's being executed.
translated_args - A table of the translated arguments, as passed into the callback function itself.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_POST_TRANSLATED_COMMAND = "ULibPostTranslatedCommand"
--[[
Hook: ULibPlayerNameChanged
Called within one second of a player changing their name.
Parameters passed to callback:
ply - The player that changed names.
oldName - The player's old name, before the change.
newName - The player's new name, after the change.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_NAME_CHANGED = "ULibPlayerNameChanged"
--[[
Section: UCL Helpers
These defines are server-only, to help with UCL.
]]
if SERVER then
ULib.UCL_LOAD_DEFAULT = true -- Set this to false to ignore the SetUserGroup() call.
ULib.UCL_USERS = "data/ulib/users.txt"
ULib.UCL_GROUPS = "data/ulib/groups.txt"
ULib.UCL_REGISTERED = "data/ulib/misc_registered.txt" -- Holds access strings that ULib has already registered
ULib.BANS_FILE = "data/ulib/bans.txt"
ULib.VERSION_FILE = "data/ulib/version.txt"
ULib.DEFAULT_GRANT_ACCESS = { allow={}, deny={}, guest=true }
hook.Add( "Initialize", "ULibCheckFileInit", function()
if ULib.fileExists( ULib.UCL_REGISTERED ) and ULib.fileExists( "addons/ulib/data/" .. ULib.UCL_GROUPS ) and ULib.fileRead( ULib.UCL_GROUPS ) == ULib.fileRead( "addons/ulib/data/" .. ULib.UCL_GROUPS ) then
-- File has been reset, delete registered
ULib.fileDelete( ULib.UCL_REGISTERED )
end
end)
end
--[[
Section: Net pooled strings
These defines are server-only, to help with the networking library.
]]
if SERVER then
util.AddNetworkString( "URPC" )
end
--[[
Title: Defines
Holds some defines used on both client and server.
]]
ULib = ULib or {}
ULib.VERSION = 2.60
ULib.ACCESS_ALL = "user"
ULib.ACCESS_OPERATOR = "operator"
ULib.ACCESS_ADMIN = "admin"
ULib.ACCESS_SUPERADMIN = "superadmin"
ULib.DEFAULT_ACCESS = ULib.ACCESS_ALL
ULib.DEFAULT_TSAY_COLOR = Color( 151, 211, 255 ) -- Found by using MS Paint
--[[
Section: Umsg Helpers
These are ids for the ULib umsg functions, so the client knows what they're getting.
]]
ULib.TYPE_ANGLE = 1
ULib.TYPE_BOOLEAN = 2
ULib.TYPE_CHAR = 3
ULib.TYPE_ENTITY = 4
ULib.TYPE_FLOAT = 5
ULib.TYPE_LONG = 6
ULib.TYPE_SHORT = 7
ULib.TYPE_STRING = 8
ULib.TYPE_VECTOR = 9
-- These following aren't actually datatypes, we handle them ourselves
ULib.TYPE_TABLE_BEGIN = 10
ULib.TYPE_TABLE_END = 11
ULib.TYPE_NIL = 12
ULib.RPC_UMSG_NAME = "URPC"
ULib.TYPE_SIZE = {
[ULib.TYPE_ANGLE] = 12, -- 3 floats
[ULib.TYPE_BOOLEAN] = 1,
[ULib.TYPE_CHAR] = 1,
[ULib.TYPE_ENTITY] = 4, -- Found through trial and error
[ULib.TYPE_FLOAT] = 4,
[ULib.TYPE_LONG] = 4,
[ULib.TYPE_SHORT] = 2,
[ULib.TYPE_VECTOR] = 12, -- 3 floats
[ULib.TYPE_NIL] = 0, -- Not technically a type but we handle it anyways
}
ULib.MAX_UMSG_BYTES = 255
--[[
Section: Hooks
These are the hooks that ULib has created that other modders are free to make use of.
]]
--[[
Hook: UCLAuthed
Called *on both server and client* when a player has been (re)authenticated by UCL. Called for ALL players, regardless of access.
Parameters passed to callback:
ply - The player that got (re)authenticated.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_UCLAUTH = "UCLAuthed"
--[[
Hook: UCLChanged
Called *on both server and client* when anything in ULib.ucl.users, ULib.ucl.authed, or ULib.ucl.groups changes. No parameters are passed to callbacks.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_UCLCHANGED = "UCLChanged"
--[[
Hook: ULibReplicatedCvarChanged
Called *on both client and server* when a replicated cvar changes or is created.
Parameters passed to callback:
sv_cvar - The name of the server-side cvar.
cl_cvar - The name of the client-side cvar.
ply - The player changing the cvar or nil on initial value.
old_value - The previous value of the cvar, nil if this call is to set the initial value.
new_value - The new value of the cvar.
Revisions:
v2.40 - Initial
v2.50 - Removed nil on client side restriction.
]]
ULib.HOOK_REPCVARCHANGED = "ULibReplicatedCvarChanged"
--[[
Hook: ULibLocalPlayerReady
Called *on both client and server* when a player entity is created. (can now run commands). Only works for local
player on the client side.
Parameters passed to callback:
ply - The player that's ready (local player on client side).
Revisions:
v2.40 - Initial
]]
ULib.HOOK_LOCALPLAYERREADY = "ULibLocalPlayerReady"
--[[
Hook: ULibCommandCalled
Called *on server* whenever a ULib command is run, return false to override and not allow, true to stop executing callbacks and allow.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
args - The table of args for the command.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_COMMAND_CALLED = "ULibCommandCalled"
--[[
Hook: ULibPlayerTarget
Called whenever one player is about to target another player. Called *BEFORE* any other validation
takes place. Return false and error message to disallow target completely, return true to
override any other validation logic and allow the target to take place, return a player to force
the target to be the specified player.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
target - The proposed target of the command before any other validation logic takes place.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_TARGET = "ULibPlayerTarget"
--[[
Hook: ULibPlayerTargets
Called whenever one player is about to target another set of players. Called *BEFORE* any other validation
takes place. Return false and error message to disallow target completely, return true to
override any other validation logic and allow the target to take place, return a table of players to force
the targets to be the specified players.
Parameters passed to callback:
ply - The player attempting to execute the command.
commandName - The command that's being executed.
targets - The proposed targets of the command before any other validation logic takes place.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_TARGETS = "ULibPlayerTargets" -- Exactly the same as the above but used when the player is using a command that can target multiple players.
--[[
Hook: ULibPostTranslatedCommand
*Server hook*. Called after a translated command (ULib.cmds.TranslatedCommand) has been successfully
verified. This hook directly follows the callback for the command itself.
Parameters passed to callback:
ply - The player that executed the command.
commandName - The command that's being executed.
translated_args - A table of the translated arguments, as passed into the callback function itself.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_POST_TRANSLATED_COMMAND = "ULibPostTranslatedCommand"
--[[
Hook: ULibPlayerNameChanged
Called within one second of a player changing their name.
Parameters passed to callback:
ply - The player that changed names.
oldName - The player's old name, before the change.
newName - The player's new name, after the change.
Revisions:
v2.40 - Initial
]]
ULib.HOOK_PLAYER_NAME_CHANGED = "ULibPlayerNameChanged"
--[[
Hook: ULibGetUsersCustomKeyword
Called during ULib.getUsers when considering a target string for keywords.
This could be used to create a new, custom keyword for targetting users who
have been connected for less than five minutes, for example.
Return nil or a table of player objects to add to the target list.
Parameters passed to callback:
target - A string chunk of a possibly larger target list to operate on.
ply - The player doing the targetting, not always specified (can be nil).
Revisions:
v2.60 - Initial
]]
ULib.HOOK_GETUSERS_CUSTOM_KEYWORD = "ULibGetUsersCustomKeyword"
--[[
Hook: ULibGetUserCustomKeyword
Called during ULib.getUser when considering a target string for keywords.
This could be used to create a new, custom keyword for always targetting a
specific connected steamid, for example. Or, to target the shortest connected
player.
Return nil or a player object.
Parameters passed to callback:
target - A string target.
ply - The player doing the targetting, not always specified (can be nil).
Revisions:
v2.60 - Initial
]]
ULib.HOOK_GETUSER_CUSTOM_KEYWORD = "ULibGetUserCustomKeyword"
--[[
Section: UCL Helpers
These defines are server-only, to help with UCL.
]]
if SERVER then
ULib.UCL_LOAD_DEFAULT = true -- Set this to false to ignore the SetUserGroup() call.
ULib.UCL_USERS = "data/ulib/users.txt"
ULib.UCL_GROUPS = "data/ulib/groups.txt"
ULib.UCL_REGISTERED = "data/ulib/misc_registered.txt" -- Holds access strings that ULib has already registered
ULib.BANS_FILE = "data/ulib/bans.txt"
ULib.VERSION_FILE = "data/ulib/version.txt"
ULib.DEFAULT_GRANT_ACCESS = { allow={}, deny={}, guest=true }
hook.Add( "Initialize", "ULibCheckFileInit", function()
if ULib.fileExists( ULib.UCL_REGISTERED ) and ULib.fileExists( "addons/ulib/data/" .. ULib.UCL_GROUPS ) and ULib.fileRead( ULib.UCL_GROUPS ) == ULib.fileRead( "addons/ulib/data/" .. ULib.UCL_GROUPS ) then
-- File has been reset, delete registered
ULib.fileDelete( ULib.UCL_REGISTERED )
end
end)
end
--[[
Section: Net pooled strings
These defines are server-only, to help with the networking library.
]]
if SERVER then
util.AddNetworkString( "URPC" )
end

View File

@ -1,337 +1,344 @@
--[[
Title: Player
Has useful player-related functions.
]]
--[[
Function: getPicker
Gets the player directly in front of the specified player
Parameters:
ply - The player to look for another player in front of.
radius - *(Optional, defaults to 30)* How narrow to make our checks for players in front of us.
Returns:
The player most directly in front of us if one exists with the given constraints, otherwise nil.
Revisions:
v2.40 - Initial.
]]
function ULib.getPicker( ply, radius )
radius = radius or 30
local trace = util.GetPlayerTrace( ply )
local trace_results = util.TraceLine( trace )
if not trace_results.Entity:IsValid() or not trace_results.Entity:IsPlayer() then
-- Try finding a best choice
local best_choice
local best_choice_diff
local pos = ply:GetPos()
local ang = ply:GetAimVector():Angle()
local players = player.GetAll()
for _, player in ipairs( players ) do
if player ~= ply then
local vec_diff = player:GetPos() - Vector( 0, 0, 16 ) - pos
local newang = vec_diff:Angle()
local diff = math.abs( math.NormalizeAngle( newang.pitch - ang.pitch ) ) + math.abs( math.NormalizeAngle( newang.yaw - ang.yaw ) )
if not best_choice_diff or diff < best_choice_diff then
best_choice_diff = diff
best_choice = player
end
end
end
if not best_choice or best_choice_diff > radius then
return -- Give up
else
return best_choice
end
else
return trace_results.Entity
end
end
local Player = FindMetaTable( "Player" )
local checkIndexes = { Player.UniqueID, function( ply ) if CLIENT then return "" end local ip = ULib.splitPort( ply:IPAddress() ) return ip end, Player.SteamID, Player.UserID }
--[[
Function: getPlyByID
Finds a user identified by the given ID.
Parameters:
id - The ID to try to match against connected players. Can be a unique id, ip address, steam id, or user id.
Returns:
The player matching the id given or nil if none match.
Revisions:
v2.50 - Initial.
]]
function ULib.getPlyByID( id )
id = id:upper()
local players = player.GetAll()
for _, indexFn in ipairs( checkIndexes ) do
for _, ply in ipairs( players ) do
if tostring( indexFn( ply ) ) == id then
return ply
end
end
end
return nil
end
--[[
Function: getUniqueIDForPly
Finds a unique ID for a player, suitable for use in getUsers or getUser to uniquely identify the given player.
Parameters:
ply - The player we want an ID for
Returns:
The id for the player or nil if none are unique.
Revisions:
v2.50 - Initial.
v2.51 - Added exception for single player since it's handled differently on client and server.
]]
function ULib.getUniqueIDForPlayer( ply )
if game.SinglePlayer() then
return "1"
end
local players = player.GetAll()
for _, indexFn in ipairs( checkIndexes ) do
local id = indexFn( ply )
if ULib.getUser( "$" .. id, true ) == ply then
return id
end
end
return nil
end
--[[
Function: getUsers
Finds users matching an identifier.
Parameters:
target - A string of what you'd like to target. Accepts a comma separated list.
enable_keywords - *(Optional, defaults to false)* If true, the keywords "*" for all players, "^" for self,
"@" for picker (person in front of you), "#<group>" for those inside a specific group,
"%<group>" for users inside a group (counting inheritance), and "$<id>" for users matching a
particular ID will be activated.
Any of these can be negated with "!" before it. IE, "!^" targets everyone but yourself.
ply - *(Optional)* Player needing getUsers, this is necessary for some of the keywords.
Returns:
A table of players (false and message if none found).
Revisions:
v2.40 - Rewrite, added more keywords, removed immunity.
v2.50 - Added "#" and '$' keywords, removed special exception for "%user" (replaced by "#user").
]]
function ULib.getUsers( target, enable_keywords, ply )
local players = player.GetAll()
-- First, do a full name match in case someone's trying to exploit our target system
for _, player in ipairs( players ) do
if target:lower() == player:Nick():lower() then
return { player }
end
end
-- Okay, now onto the show!
local targetPlys = {}
local pieces = ULib.explode( ",", target )
for _, piece in ipairs( pieces ) do
piece = piece:Trim()
if piece ~= "" then
local keywordMatch = false
if enable_keywords then
local tmpTargets = {}
local negate = false
if piece:sub( 1, 1 ) == "!" and piece:len() > 1 then
negate = true
piece = piece:sub( 2 )
end
if piece:sub( 1, 1 ) == "$" then
local player = ULib.getPlyByID( piece:sub( 2 ) )
if player then
table.insert( tmpTargets, player )
end
elseif piece == "*" then -- All!
table.Add( tmpTargets, players )
elseif piece == "^" then -- Self!
if ply then
if ply:IsValid() then
table.insert( tmpTargets, ply )
elseif not negate then
return false, "You cannot target yourself from console!"
end
end
elseif piece == "@" then
if IsValid( ply ) then
local player = ULib.getPicker( ply )
if player then
table.insert( tmpTargets, player )
end
end
elseif piece:sub( 1, 1 ) == "#" and ULib.ucl.groups[ piece:sub( 2 ) ] then
local group = piece:sub( 2 )
for _, player in ipairs( players ) do
if player:GetUserGroup() == group then
table.insert( tmpTargets, player )
end
end
elseif piece:sub( 1, 1 ) == "%" and ULib.ucl.groups[ piece:sub( 2 ) ] then
local group = piece:sub( 2 )
for _, player in ipairs( players ) do
if player:CheckGroup( group ) then
table.insert( tmpTargets, player )
end
end
end
if negate then
for _, player in ipairs( players ) do
if not table.HasValue( tmpTargets, player ) then
keywordMatch = true
table.insert( targetPlys, player )
end
end
else
if #tmpTargets > 0 then
keywordMatch = true
table.Add( targetPlys, tmpTargets )
end
end
end
if not keywordMatch then
for _, player in ipairs( players ) do
if player:Nick():lower():find( piece:lower(), 1, true ) then -- No patterns
table.insert( targetPlys, player )
end
end
end
end
end
-- Now remove duplicates
local finalTable = {}
for _, player in ipairs( targetPlys ) do
if not table.HasValue( finalTable, player ) then
table.insert( finalTable, player )
end
end
if #finalTable < 1 then
return false, "No target found or target has immunity!"
end
return finalTable
end
--[[
Function: getUser
Finds a user matching an identifier.
Parameters:
target - A string of the user you'd like to target. IE, a partial player name.
enable_keywords - *(Optional, defaults to false)* If true, the keywords "^" for self, "@" for picker (person in
front of you), and "$<id>" will be activated.
ply - *(Optional)* Player needing getUsers, this is necessary to use keywords.
Returns:
The resulting player target, false and message if no user found.
Revisions:
v2.40 - Rewrite, added keywords, removed immunity.
v2.50 - Added "$" keyword.
]]
function ULib.getUser( target, enable_keywords, ply )
local players = player.GetAll()
target = target:lower()
local plyMatches = {}
if enable_keywords and target:sub( 1, 1 ) == "$" then
possibleId = target:sub( 2 )
table.insert( plyMatches, ULib.getPlyByID( possibleId ) )
end
-- First, do a full name match in case someone's trying to exploit our target system
for _, player in ipairs( players ) do
if target == player:Nick():lower() then
if #plyMatches == 0 then
return player
else
return false, "Found multiple targets! Please choose a better string for the target. (EG, the whole name)"
end
end
end
if enable_keywords then
if target == "^" and ply then
if ply:IsValid() then
return ply
else
return false, "You cannot target yourself from console!"
end
elseif IsValid( ply ) and target == "@" then
local player = ULib.getPicker( ply )
if not player then
return false, "No player found in the picker"
else
return player
end
end
end
for _, player in ipairs( players ) do
if player:Nick():lower():find( target, 1, true ) then -- No patterns
table.insert( plyMatches, player )
end
end
if #plyMatches == 0 then
return false, "No target found or target has immunity!"
elseif #plyMatches > 1 then
local str = plyMatches[ 1 ]:Nick()
for i=2, #plyMatches do
str = str .. ", " .. plyMatches[ i ]:Nick()
end
return false, "Found multiple targets: " .. str .. ". Please choose a better string for the target. (EG, the whole name)"
end
return plyMatches[ 1 ]
end
--[[
Title: Player
Has useful player-related functions.
]]
--[[
Function: getPicker
Gets the player directly in front of the specified player
Parameters:
ply - The player to look for another player in front of.
radius - *(Optional, defaults to 30)* How narrow to make our checks for players in front of us.
Returns:
The player most directly in front of us if one exists with the given constraints, otherwise nil.
Revisions:
v2.40 - Initial.
]]
function ULib.getPicker( ply, radius )
radius = radius or 30
local trace = util.GetPlayerTrace( ply )
local trace_results = util.TraceLine( trace )
if not trace_results.Entity:IsValid() or not trace_results.Entity:IsPlayer() then
-- Try finding a best choice
local best_choice
local best_choice_diff
local pos = ply:GetPos()
local ang = ply:GetAimVector():Angle()
local players = player.GetAll()
for _, player in ipairs( players ) do
if player ~= ply then
local vec_diff = player:GetPos() - Vector( 0, 0, 16 ) - pos
local newang = vec_diff:Angle()
local diff = math.abs( math.NormalizeAngle( newang.pitch - ang.pitch ) ) + math.abs( math.NormalizeAngle( newang.yaw - ang.yaw ) )
if not best_choice_diff or diff < best_choice_diff then
best_choice_diff = diff
best_choice = player
end
end
end
if not best_choice or best_choice_diff > radius then
return -- Give up
else
return best_choice
end
else
return trace_results.Entity
end
end
local Player = FindMetaTable( "Player" )
local checkIndexes = { Player.UniqueID, function( ply ) if CLIENT then return "" end local ip = ULib.splitPort( ply:IPAddress() ) return ip end, Player.SteamID, Player.UserID }
--[[
Function: getPlyByID
Finds a user identified by the given ID.
Parameters:
id - The ID to try to match against connected players. Can be a unique id, ip address, steam id, or user id.
Returns:
The player matching the id given or nil if none match.
Revisions:
v2.50 - Initial.
]]
function ULib.getPlyByID( id )
id = id:upper()
local players = player.GetAll()
for _, indexFn in ipairs( checkIndexes ) do
for _, ply in ipairs( players ) do
if tostring( indexFn( ply ) ) == id then
return ply
end
end
end
return nil
end
--[[
Function: getUniqueIDForPly
Finds a unique ID for a player, suitable for use in getUsers or getUser to uniquely identify the given player.
Parameters:
ply - The player we want an ID for
Returns:
The id for the player or nil if none are unique.
Revisions:
v2.50 - Initial.
v2.51 - Added exception for single player since it's handled differently on client and server.
]]
function ULib.getUniqueIDForPlayer( ply )
if game.SinglePlayer() then
return "1"
end
local players = player.GetAll()
for _, indexFn in ipairs( checkIndexes ) do
local id = indexFn( ply )
if ULib.getUser( "$" .. id, true ) == ply then
return id
end
end
return nil
end
--[[
Function: getUsers
Finds users matching an identifier.
Parameters:
target - A string of what you'd like to target. Accepts a comma separated list.
enable_keywords - *(Optional, defaults to false)* If true, the keywords "*" for all players, "^" for self,
"@" for picker (person in front of you), "#<group>" for those inside a specific group,
"%<group>" for users inside a group (counting inheritance), and "$<id>" for users matching a
particular ID will be activated.
Any of these can be negated with "!" before it. IE, "!^" targets everyone but yourself.
ply - *(Optional)* Player needing getUsers, this is necessary for some of the keywords.
Returns:
A table of players (false and message if none found).
Revisions:
v2.40 - Rewrite, added more keywords, removed immunity.
v2.50 - Added "#" and '$' keywords, removed special exception for "%user" (replaced by "#user").
]]
function ULib.getUsers( target, enable_keywords, ply )
local players = player.GetAll()
-- First, do a full name match in case someone's trying to exploit our target system
for _, player in ipairs( players ) do
if target:lower() == player:Nick():lower() then
return { player }
end
end
-- Okay, now onto the show!
local targetPlys = {}
local pieces = ULib.explode( ",", target )
for _, piece in ipairs( pieces ) do
piece = piece:Trim()
if piece ~= "" then
local keywordMatch = false
if enable_keywords then
local tmpTargets = {}
local negate = false
if piece:sub( 1, 1 ) == "!" and piece:len() > 1 then
negate = true
piece = piece:sub( 2 )
end
if piece:sub( 1, 1 ) == "$" then
local player = ULib.getPlyByID( piece:sub( 2 ) )
if player then
table.insert( tmpTargets, player )
end
elseif piece == "*" then -- All!
table.Add( tmpTargets, players )
elseif piece == "^" then -- Self!
if ply then
if ply:IsValid() then
table.insert( tmpTargets, ply )
elseif not negate then
return false, "You cannot target yourself from console!"
end
end
elseif piece == "@" then
if IsValid( ply ) then
local player = ULib.getPicker( ply )
if player then
table.insert( tmpTargets, player )
end
end
elseif piece:sub( 1, 1 ) == "#" and ULib.ucl.groups[ piece:sub( 2 ) ] then
local group = piece:sub( 2 )
for _, player in ipairs( players ) do
if player:GetUserGroup() == group then
table.insert( tmpTargets, player )
end
end
elseif piece:sub( 1, 1 ) == "%" and ULib.ucl.groups[ piece:sub( 2 ) ] then
local group = piece:sub( 2 )
for _, player in ipairs( players ) do
if player:CheckGroup( group ) then
table.insert( tmpTargets, player )
end
end
else
local tblForHook = hook.Run( ULib.HOOK_GETUSERS_CUSTOM_KEYWORD, piece, ply )
if tblForHook then
table.Add( tmpTargets, tblForHook )
end
end
if negate then
for _, player in ipairs( players ) do
if not table.HasValue( tmpTargets, player ) then
keywordMatch = true
table.insert( targetPlys, player )
end
end
else
if #tmpTargets > 0 then
keywordMatch = true
table.Add( targetPlys, tmpTargets )
end
end
end
if not keywordMatch then
for _, player in ipairs( players ) do
if player:Nick():lower():find( piece:lower(), 1, true ) then -- No patterns
table.insert( targetPlys, player )
end
end
end
end
end
-- Now remove duplicates
local finalTable = {}
for _, player in ipairs( targetPlys ) do
if not table.HasValue( finalTable, player ) then
table.insert( finalTable, player )
end
end
if #finalTable < 1 then
return false, "No target found or target has immunity!"
end
return finalTable
end
--[[
Function: getUser
Finds a user matching an identifier.
Parameters:
target - A string of the user you'd like to target. IE, a partial player name.
enable_keywords - *(Optional, defaults to false)* If true, the keywords "^" for self, "@" for picker (person in
front of you), and "$<id>" will be activated.
ply - *(Optional)* Player needing getUsers, this is necessary to use keywords.
Returns:
The resulting player target, false and message if no user found.
Revisions:
v2.40 - Rewrite, added keywords, removed immunity.
v2.50 - Added "$" keyword.
]]
function ULib.getUser( target, enable_keywords, ply )
local players = player.GetAll()
target = target:lower()
local plyMatches = {}
if enable_keywords and target:sub( 1, 1 ) == "$" then
possibleId = target:sub( 2 )
table.insert( plyMatches, ULib.getPlyByID( possibleId ) )
end
-- First, do a full name match in case someone's trying to exploit our target system
for _, player in ipairs( players ) do
if target == player:Nick():lower() then
if #plyMatches == 0 then
return player
else
return false, "Found multiple targets! Please choose a better string for the target. (EG, the whole name)"
end
end
end
if enable_keywords then
if target == "^" and ply then
if ply:IsValid() then
return ply
else
return false, "You cannot target yourself from console!"
end
elseif IsValid( ply ) and target == "@" then
local player = ULib.getPicker( ply )
if not player then
return false, "No player found in the picker"
else
return player
end
else
local player = hook.Run( ULib.HOOK_GETUSER_CUSTOM_KEYWORD, target, ply )
if player then return player end
end
end
for _, player in ipairs( players ) do
if player:Nick():lower():find( target, 1, true ) then -- No patterns
table.insert( plyMatches, player )
end
end
if #plyMatches == 0 then
return false, "No target found or target has immunity!"
elseif #plyMatches > 1 then
local str = plyMatches[ 1 ]:Nick()
for i=2, #plyMatches do
str = str .. ", " .. plyMatches[ i ]:Nick()
end
return false, "Found multiple targets: " .. str .. ". Please choose a better string for the target. (EG, the whole name)"
end
return plyMatches[ 1 ]
end