454 lines
10 KiB
Lua
454 lines
10 KiB
Lua
|
|
-- Copyright (C) 2017-2020 DBotThePony
|
|
|
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
-- of this software and associated documentation files (the "Software"), to deal
|
|
-- in the Software without restriction, including without limitation the rights
|
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
-- of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
-- subject to the following conditions:
|
|
|
|
-- The above copyright notice and this permission notice shall be included in all copies
|
|
-- or substantial portions of the Software.
|
|
|
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
-- DEALINGS IN THE SOFTWARE.
|
|
|
|
jit.on()
|
|
|
|
local util = setmetatable(DLib.util or {}, {__index = util})
|
|
DLib.util = util
|
|
local DLib = DLib
|
|
local vgui = vgui
|
|
local type = luatype
|
|
local ipairs = ipairs
|
|
local IsValid = IsValid
|
|
local LVector = LVector
|
|
local Vector = Vector
|
|
local Angle = Angle
|
|
|
|
if CLIENT then
|
|
concommand.Add('dlib_gui_openurl', function(_, _, args)
|
|
return gui.OpenURL(table.concat(args, '%20'))
|
|
end)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.VCreate
|
|
@args string panelName, Panel parent
|
|
|
|
@desc
|
|
!g:vgui.Create but does not create panel when parent is not present
|
|
@enddesc
|
|
|
|
@returns
|
|
Panel: created panel
|
|
]]
|
|
function DLib.VCreate(pnlName, pnlParent)
|
|
if not IsValid(pnlParent) then
|
|
DLib.Message(debug.traceback('Attempt to create ' .. pnlName .. ' without valid parent!', 2))
|
|
return
|
|
end
|
|
|
|
return vgui.Create(pnlName, pnlParent)
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.Clone
|
|
@args any variable
|
|
|
|
@desc
|
|
creates better copy of variable.
|
|
can clone tables better than !g:table.Copy
|
|
**NOT RECURSION SAFE**
|
|
**will attempt to throw an error when encounters a recursion**
|
|
@enddesc
|
|
|
|
@returns
|
|
any: clone
|
|
]]
|
|
function util.copy(var)
|
|
if type(var) == 'table' then
|
|
local output = {}
|
|
|
|
for k, v in pairs(var) do
|
|
if k == var or v == var then
|
|
error('Confused by recursion')
|
|
end
|
|
|
|
output[util.Copy(k)] = util.Copy(v)
|
|
end
|
|
|
|
return output
|
|
end
|
|
|
|
if type(var) == 'Angle' then return Angle(var.p, var.y, var.r) end
|
|
if type(var) == 'Vector' then return Vector(var) end
|
|
if type(var) == 'LVector' then return LVector(var) end
|
|
return var
|
|
end
|
|
|
|
util.clone = util.copy
|
|
util.Clone = util.copy
|
|
util.Copy = util.copy
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.VectorRand
|
|
@alias DLib.util.RandomVector
|
|
@args number x, number y, number z
|
|
|
|
@desc
|
|
calls math.Rand(-component, component) for each component
|
|
@enddesc
|
|
|
|
@returns
|
|
Vector
|
|
]]
|
|
function util.randomVector(mx, my, mz)
|
|
return Vector(math.Rand(-mx, mx), math.Rand(-my, my), math.Rand(-mz, mz))
|
|
end
|
|
|
|
util.RandomVector = util.randomVector
|
|
util.VectorRand = util.randomVector
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.ComposeEnums
|
|
@args table input, vararg ...numbers
|
|
|
|
@desc
|
|
:bor()'s numbers in `input` and :bor()'s numbers in vararg towards newly created value
|
|
@enddesc
|
|
|
|
@returns
|
|
number
|
|
]]
|
|
function util.composeEnums(input, ...)
|
|
local num = 0
|
|
|
|
if type(input) == 'table' then
|
|
for k, v in ipairs(input) do
|
|
num = num:bor(v)
|
|
end
|
|
else
|
|
num = input
|
|
end
|
|
|
|
return num:bor(...)
|
|
end
|
|
|
|
util.ComposeEnums = util.composeEnums
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.ValidateSteamID
|
|
@args string steamid
|
|
|
|
@returns
|
|
boolean: whenever string provided is regular steamid (STEAM_0)
|
|
]]
|
|
function util.ValidateSteamID(input)
|
|
if not input then return false end
|
|
return input:match('STEAM_0:[0-1]:[0-9]+$') ~= nil
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.SteamLink
|
|
@args string steamid
|
|
|
|
@desc
|
|
accepts steamid and steamid64
|
|
@enddesc
|
|
|
|
@returns
|
|
string
|
|
]]
|
|
function util.SteamLink(steamid)
|
|
if util.ValidateSteamID(steamid) then
|
|
return 'https://steamcommunity.com/profiles/' .. util.SteamIDTo64(steamid) .. '/'
|
|
else
|
|
return 'https://steamcommunity.com/profiles/' .. steamid .. '/'
|
|
end
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.CreateSharedConvar
|
|
@args string cvarname, string cvardef, string description
|
|
|
|
@desc
|
|
quick way to workaround [this](https://github.com/Facepunch/garrysmod-issues/issues/3323)
|
|
adds `FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY` flags serverside and only `FCVAR_REPLICATED` clientside
|
|
@enddesc
|
|
|
|
@returns
|
|
ConVar
|
|
]]
|
|
function util.CreateSharedConvar(cvarname, cvarvalue, description)
|
|
if CLIENT then
|
|
return CreateConVar(cvarname, cvarvalue, {FCVAR_REPLICATED}, description)
|
|
else
|
|
return CreateConVar(cvarname, cvarvalue, {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_NOTIFY}, description)
|
|
end
|
|
end
|
|
|
|
-- Replace PrintTable with something better
|
|
|
|
local rawtype = rawtype
|
|
local table = table
|
|
local error = error
|
|
local pcall = pcall
|
|
local debug = debug
|
|
local strict = false
|
|
|
|
local RECURSION_COLOR = Color(195, 222, 69)
|
|
local TOO_DEEP_COLOR = Color(196, 158, 91)
|
|
local EQUALS_COLOR = Color(169, 117, 222)
|
|
local TABLE_TOKEN_COLOR = Color(197, 104, 111)
|
|
local COMMENTARY_COLOR = Color(143, 165, 46)
|
|
|
|
DLib._OldPrintTable = DLib._OldPrintTable or PrintTable
|
|
|
|
local wellknown, prints
|
|
local unmap = {}
|
|
|
|
for i = 0, 16 do
|
|
unmap[string.char(i)] = '\\x' .. string.format('%.2X', i)
|
|
end
|
|
|
|
local function getValueString(typeIn, valueIn)
|
|
if typeIn == 'string' then
|
|
return DLib.DEFAULT_TEXT_COLOR, '"' .. valueIn:gsub('"', '\\"'):gsub('\t', '\\t'):gsub('\n', '\\n'):gsub('.', function(str) return unmap[str] or str end) .. '"'
|
|
end
|
|
|
|
return DLib.GetPrettyPrint(valueIn, typeIn)
|
|
end
|
|
|
|
function util.PrintableString(valueIn)
|
|
return valueIn:gsub('"', '\\"'):gsub('\t', '\\t'):gsub('\n', '\\n'):gsub('.', function(str) return unmap[str] or str end), nil
|
|
end
|
|
|
|
local comparableTypes = {}
|
|
|
|
local function InternalPrintLoop(tableIn, level, recursionCheck)
|
|
if prints > 100000 then
|
|
error('I dont want to print more. Probably hit a recursion.')
|
|
end
|
|
|
|
if strict then
|
|
if wellknown[tableIn] then
|
|
MsgC(RECURSION_COLOR, ' [well known/recursion] ')
|
|
return false
|
|
end
|
|
else
|
|
if wellknown[tableIn] and wellknown[tableIn] > 6 then
|
|
MsgC(RECURSION_COLOR, ' [well known] ')
|
|
return false
|
|
end
|
|
|
|
if recursionCheck and recursionCheck[tableIn] then
|
|
MsgC(RECURSION_COLOR, ' [recursion] ')
|
|
return false
|
|
end
|
|
end
|
|
|
|
if level > 10 then
|
|
MsgC(TOO_DEEP_COLOR, ' [too deep] ')
|
|
return false
|
|
end
|
|
|
|
local keys = {}
|
|
|
|
for k in pairs(tableIn) do
|
|
table.insert(keys, k)
|
|
end
|
|
|
|
table.sort(keys, function(a, b)
|
|
local ta, tb = type(a), type(b)
|
|
local cmp = false
|
|
local token = ta .. '-' .. tb
|
|
|
|
if comparableTypes[token] == nil then
|
|
comparableTypes[token] = pcall(function()
|
|
cmp = a < b
|
|
end)
|
|
elseif comparableTypes[token] then
|
|
cmp = a < b
|
|
end
|
|
|
|
return cmp
|
|
end)
|
|
|
|
wellknown[tableIn] = (wellknown[tableIn] or 0) + 1
|
|
|
|
local hitAnything = false
|
|
|
|
for i, key in ipairs(keys) do
|
|
prints = prints + 1
|
|
|
|
if not hitAnything then
|
|
MsgC('\n')
|
|
hitAnything = true
|
|
end
|
|
|
|
local ktp = type(key)
|
|
MsgC(string.rep(' ', level * 4), TABLE_TOKEN_COLOR, '[')
|
|
MsgC(getValueString(ktp, key))
|
|
MsgC(TABLE_TOKEN_COLOR, ']', EQUALS_COLOR, ' = ')
|
|
local value = tableIn[key]
|
|
|
|
if type(value) == 'table' then
|
|
MsgC(TABLE_TOKEN_COLOR, '{')
|
|
local useSpaces = InternalPrintLoop(value, level + 1, strict and '' or recursionCheck or {})
|
|
|
|
if useSpaces then
|
|
MsgC(TABLE_TOKEN_COLOR, string.rep(' ', level * 4), '},\n')
|
|
else
|
|
MsgC(TABLE_TOKEN_COLOR, '},\n')
|
|
end
|
|
else
|
|
local tp = type(value)
|
|
MsgC(getValueString(tp, value))
|
|
MsgC(TABLE_TOKEN_COLOR, ',\n')
|
|
end
|
|
end
|
|
|
|
if not hitAnything then
|
|
MsgC(COMMENTARY_COLOR, ' --[[ empty ]] ')
|
|
end
|
|
|
|
return hitAnything
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname PrintTable
|
|
@replaces
|
|
@args table value
|
|
|
|
@desc
|
|
pretty prints a table.
|
|
attempts to avoid recursion.
|
|
@enddesc
|
|
]]
|
|
function _G.PrintTable(tableIn)
|
|
assert(rawtype(tableIn) == 'table', 'Input must be a table!')
|
|
wellknown = {}
|
|
prints = 0
|
|
strict = false
|
|
MsgC(TABLE_TOKEN_COLOR, '{')
|
|
InternalPrintLoop(tableIn, 1)
|
|
MsgC(TABLE_TOKEN_COLOR, '}\n')
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname PrintTableStrict
|
|
@args table value
|
|
|
|
@desc
|
|
pretty prints a table.
|
|
strictly avoids any meaning of recursion.
|
|
@enddesc
|
|
]]
|
|
function _G.PrintTableStrict(tableIn)
|
|
assert(rawtype(tableIn) == 'table', 'Input must be a table!')
|
|
wellknown = {}
|
|
prints = 0
|
|
strict = true
|
|
MsgC(TABLE_TOKEN_COLOR, '{')
|
|
InternalPrintLoop(tableIn, 1)
|
|
MsgC(TABLE_TOKEN_COLOR, '}\n')
|
|
end
|
|
|
|
local files = file.Find('scripts/weapons/*.txt', 'GAME')
|
|
local wepdata = {}
|
|
|
|
for i, mfile in ipairs(files) do
|
|
local parsed = util.KeyValuesToTable(file.Read('scripts/weapons/' .. mfile, 'GAME'))
|
|
|
|
if parsed then
|
|
wepdata[mfile:sub(1, -5)] = parsed
|
|
end
|
|
end
|
|
|
|
if CLIENT then
|
|
for wepclass, data in pairs(wepdata) do
|
|
if data.texturedata then
|
|
if data.texturedata.weapon and data.texturedata.weapon.file then
|
|
data.texturedata.weapon.material = Material(data.texturedata.weapon.file)
|
|
end
|
|
|
|
if data.texturedata.weapon_s and data.texturedata.weapon_s.file then
|
|
data.texturedata.weapon_s.material = Material(data.texturedata.weapon_s.file)
|
|
end
|
|
|
|
if data.texturedata.ammo and data.texturedata.ammo.file then
|
|
data.texturedata.ammo.material = Material(data.texturedata.ammo.file)
|
|
end
|
|
|
|
if data.texturedata.ammo2 and data.texturedata.ammo2.file then
|
|
data.texturedata.ammo2.material = Material(data.texturedata.ammo2.file)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname DLib.util.GetWeaponScript
|
|
@args Weapon weaponIn
|
|
|
|
@returns
|
|
table: or nil, if no script present
|
|
]]
|
|
function DLib.util.GetWeaponScript(weaponIn)
|
|
if isstring(weaponIn) then
|
|
return wepdata[weaponIn]
|
|
elseif type(weaponIn) == 'Weapon' then
|
|
return wepdata[weaponIn:GetClass()]
|
|
else
|
|
error('Invalid weapon provided: ' .. type(weaponIn))
|
|
end
|
|
end
|
|
|
|
--[[
|
|
@doc
|
|
@fname printflags
|
|
@args number flags, number minimumDecimals = 0
|
|
|
|
@returns
|
|
string: formatted
|
|
]]
|
|
function _G.printflags(flagsIn, mnum)
|
|
assert(type(flagsIn) == 'number', 'flagsIn must be a number')
|
|
mnum = mnum or 0
|
|
local format = ''
|
|
|
|
if flagsIn < 0 then
|
|
flagsIn = flagsIn + 0xFFFFFFFF
|
|
end
|
|
|
|
flagsIn = flagsIn:floor()
|
|
|
|
while flagsIn > 0 do
|
|
local div = flagsIn % 2
|
|
flagsIn = (flagsIn - div) / 2
|
|
format = div .. format
|
|
end
|
|
|
|
if #format < mnum then
|
|
format = string.rep('0', mnum - #format) .. format
|
|
end
|
|
|
|
print(format)
|
|
return format
|
|
end
|