A collection of helpful tools for any GMod developer.
Gangbox (also gang box) is a colloquial term utilized in the construction industry, referring to a toolbox or workbox that can be accessed by multiple workers.
Gangbox offers a lot of useful, time-saving modules. Here are a few of the major ones:
⚠️ Alerts for no-op or silent failures in the GMod API↔️ Git-Like Table Diffs- 🗑️ Helpful Garbage Collector functions
- 🔎 Asynchronous file finding
- 🔢 Bitflag pretty-printing
- 🎲 Functions to return random datasets for testing
- 📝 Table helper functions (like Map/Filter)
- 🧵 Super simple Threading functions to run large amounts of work asynchronously
- 🎨 ANSI Color output for server terminal coloring
Alert modules simply give you printed feedback if you mess something up and it's not immediately clear what you did.
In GMod, if you do:
file.Write( "test.blah", "this fails because .blah is not a valid suffix" )
-- or
file.Write( "test", "this fails because it has no suffix" )
It will simply do nothing, because the filename does not have a valid suffix.
With Gangbox, a message is printed (and a non-halting error thrown) in your terminal/output if you write to an invalid suffix:
If you add a Callback function to an entity with an invalid function, or an invalid hook, AddCallback
will return nil
Developers typically don't check for this, because realistically it's just a development mistake.
As a result, there's really no way to know that this failed. Gangbox prints an alert and raises a (non-halting) error if this happens, giving you visibility to an otherwise frustrating situation:
Sometimes you need to compare two similar, but different tables. Usually this means you print both tables and manually compare them.
But that's tedious. Gangbox offers a function that prints a git-like Diff between two tables.
table t1
- The table to compare
t2
against
- The table to compare
table t2
- The table to compare against
t1
- The table to compare against
Diff two similar entities save tables
gb.Diff( Entity( 106 ):GetSaveTable(), Entity( 109 ):GetSaveTable() )
If you ever need to find certain files by name, the asynchronous gb.Find
function is the tool for you.
hALBm85MGt.mp4
gb.Find
will asynchronously search through all of the Game files and print a list of any files that match your string search.
You may also use the gbfind
concommand.
string search
- The substring to search for in all qualifying file names
Searches all files for the string "wiremonitorbig"
gb.Find( "wiremonitorbig" )
Or, in your console:
gbfind wiremonitorbig
Bitflags are common in Garry's Mod. Unfortunately, they're difficult to understand on their own.
Gangbox's ParseFlags
function will parse any Bitflag in the game and print each flag that makes up the full bitflag.
Print all of the flags within a given Bitflag.
any subject
- The bitflag, or subject containing the Bitflag.
- If
subject
is a number, the second parameter (flagSet
) is required to tell the function what kind of Bitflag it is. - Can be one of:
number
: The bitflag number itself (flagSet
must be provided)ConVar
: Prints the ConVar'sFCVAR_
flagsIMaterial
: Prints the Material's$flags
and$flags2
detailsEntity
: Prints the Entity's Engine (EFL_
), Spawn (SF_
), and Solid (FSOLID_
) flagsCTakeDamageInfo
: Prints the DamageInfo's Damage Type (DMG_
) flagsTraceResult
struct: Prints the Surface (SURF_
) or Displacement Surface (DISPSURF_
) flags
table? flagSet
(optional)- A bitflag table to parse the given
subject
with - (You'll likely want to use one of the flagsets stored in
gb.Bitflags
)
- A bitflag table to parse the given
Parse a convar's Bitflags
gb.ParseFlags( GetConVar( "example_convar" ) )
Sometimes while developing, you just need some data. It doesn't matter specifically what data you have, you just need some.
Gangbox offers a collection of Random functions that return whatever data type(s) you need.
Flips a coin, but with an optional weight.
number trueWeight
- Adjusts the likelihood of returning 'true'
- (e.g.
1
means equal chance,2
means double chance of true,0.5
means half chance oftrue
)
"Flip a coin" - 50/50 chance of true
/false
:
-- These are the same
gb.RandomBool()
true
gb.RandomBool( 1 )
false
80% chance (4x) of returning true
:
gb.RandomBool( 4 )
true
gb.RandomBool( 4 )
true
gb.RandomBool( 4 )
true
gb.RandomBool( 4 )
true
gb.RandomBool( 4 )
false
Returns a random float with the given scale
number scale
- The scale of the float (from
-scale
toscale
)
- The scale of the float (from
Return a random float between -20
and 20
:
gb.RandomFloat( 20 )
-7.9041241188604
gb.RandomFloat( 20 )
-5.7258202419517
gb.RandomFloat( 20 )
-15.256068461918
gb.RandomFloat( 20 )
12.474272548027
gb.RandomFloat( 20 )
-0.72508905413891
Returns a random whole number with the given scale
number scale
- The scale of the int (from
-scale
toscale
)
- The scale of the int (from
Return a random int between -5
and 5
:
gb.RandomInt( 5 )
3
gb.RandomInt( 5 )
1
gb.RandomInt( 5 )
-1
gb.RandomInt( 5 )
2
gb.RandomInt( 5 )
1
Returns a random number (either a float or an integer, equal chance).
number scale
- The scale of the number (from
-scale
toscale
)
- The scale of the number (from
Return a random number between -40
and 40
:
gb.RandomNumber( 40 )
-10.120163536926
gb.RandomNumber( 40 )
-36.65331504445
gb.RandomNumber( 40 )
-4
gb.RandomNumber( 40 )
-12.704684187564
gb.RandomNumber( 40 )
38.994572482706
Returns a random string with the given length, and optionally, a utf8 character range.
number len
- The end-length of the string
number minChar
(optional)- The minimum utf8 character to use
- (defaults to 97)
numer maxChar
(optional)- The maximum utf8 character to use
- (defaults to 122)
Return a random string with 30 character
gb.RandomString( 30 )
-- 21 B
"eqrzfblsawmvdbqoougbf"
Returns a random Entity on the map. This is Shared-safe, meaning it will not return clientside entities on the Client.
Return a random Entity
gb.RandomEnt()
-- weapon_crossbow
-- models/weapons/w_crossbow.mdl
Entity (94) {}
-- 1 total entry.
Returns a random Vector containing random values within the given scale
number scale
(optional)- The scale of the numbers within the Vector
- (defaults to
1
)
Return a random Vector with values between -10
and 10
gb.RandomVec( 10 )
Vector (8.5804996490479, -7.8739347457886, -8.8563461303711)
Returns a random Angle with values between -360
and 360
Return a random Angle
gb.RandomAngle()
Angle (313, -12, 174)
gb.RandomAngle()
Angle (3, 333, -307)
gb.RandomAngle()
Angle (329, 157, -249)
gb.RandomAngle()
Angle (-332, -295, -175)
gb.RandomAngle()
Angle (-167, -315, 310)
Fills the given table with random data
table tbl
- The numerically-indexed table to fill with random data
number count
(optional)- The total number of elements to insert
- (defaults to
1000
)
Fill a global table with 10 elements
mytbl = {}
gb.RandomFillTable( mytbl, 10 )
mytbl
-- 0xe18c4c32
{
[ 1] = Color ( 32, 203, 61, 255),
[ 2] = Angle ( 331 , 9 , -293 ),
[ 3] = Entity ( 84) --[[ weapon_crowbar, models/weapons/w_crowbar.mdl ]],
[ 4] = Angle ( 295 , - 36 , 326 ),
[ 5] = Color (195, 169, 179, 255),
[ 6] = Angle (- 42 , -173 , 78 ),
[ 7] = Angle ( 341 , 218 , 327 ),
[ 8] = - 0.84973641542287,
[ 9] = "frquqhozabmzeycclhzxsbyasnglyhlshrznnxkgsiqsskret",
[10] = Entity (105) --[[ viewmodel ]]
}
Returns a table filled with random elements.
number count
- The total number of elements present in the return table
Create a table with 10 random elements
gb.RandomTable( 10 )
{
[ 1] = Vector ( 0.75274169445038 , -0.60511749982834 , -0.17379862070084 ),
[ 2] = Vector (-0.66277325153351 , -0.6087372303009 , -0.71377575397491 ),
[ 3] = Vector ( 0.99587422609329 , 0.72514963150024 , 0.67650163173676 ),
[ 4] = Vector ( 0.49982884526253 , 0.34409433603287 , 0.82090830802917 ),
[ 5] = false,
[ 6] = Vector (-0.10748841613531 , -0.0051460382528603, 0.76348125934601 ),
[ 7] = -0.31730214164516,
[ 8] = 0.59660673127015,
[ 9] = Color (154, 7, 252, 255),
[10] = Color (228, 65, 213, 255)
}
Gangbox ships with a number of table helpers that make sorting through / processing large tables a breeze.
Filters the given table using the given function.
Returns a table containing elements that pass the filter.
table tbl
- The table to filter
function comp
- The filtering function, takes a single parameter:
any element
: An element in the table.- Return
true
to keep the element in the output - Return
false
/nil
to exclude the element from the output
- Return
- The filtering function, takes a single parameter:
Filter all Entities, returning only weapons
gb.Filter( ents.GetAll(), function( e ) return e.IsWeapon and e:IsWeapon() end )
{
[15] = Entity ( 84) --[[ weapon_crowbar , models/weapons/w_crowbar.mdl ]],
[21] = Entity ( 90) --[[ weapon_pistol , models/weapons/w_pistol.mdl ]],
[22] = Entity ( 91) --[[ weapon_smg1 , models/weapons/w_smg1.mdl ]],
[23] = Entity ( 92) --[[ weapon_frag , models/weapons/w_grenade.mdl ]],
[24] = Entity ( 93) --[[ weapon_physcannon, models/weapons/w_Physics.mdl ]],
[25] = Entity ( 94) --[[ weapon_crossbow , models/weapons/w_crossbow.mdl ]],
[26] = Entity ( 95) --[[ weapon_shotgun , models/weapons/w_shotgun.mdl ]],
[27] = Entity ( 96) --[[ weapon_357 , models/weapons/w_357.mdl ]],
[28] = Entity ( 97) --[[ weapon_rpg , models/weapons/w_rocket_launcher.mdl ]],
[29] = Entity ( 98) --[[ weapon_ar2 , models/weapons/w_irifle.mdl ]],
[30] = Entity ( 99) --[[ gmod_tool , models/weapons/w_toolgun.mdl ]],
[31] = Entity (100) --[[ gmod_camera , models/MaxOfS2D/camera.mdl ]],
[32] = Entity (101) --[[ weapon_physgun , models/weapons/w_Physics.mdl ]]
}
Runs the given function on all elements in the given table.
Returns a table containing the results of running the map function on each element in the table.
table tbl
- The table to map over
function comp
- The mapping function, takes a single parameter:
any element
: An element in the table.
- The mapping function, takes a single parameter:
Returns the type
of all elements in the given table
mytbl = gb.RandomTable( 5 )
{
[1] = Entity ( 94) --[[ weapon_crossbow, models/weapons/w_crossbow.mdl ]],
[2] = Entity (101) --[[ weapon_physgun , models/weapons/w_Physics.mdl ]],
[3] = true,
[4] = Entity (105) --[[ viewmodel ]],
[5] = Color ( 18, 185, 172, 255)
}
-- 5 total entries.
gb.Map( mytbl, function( e ) return type( e ) end )
{
[1] = "Weapon",
[2] = "Weapon",
[3] = "boolean",
[4] = "Entity",
[5] = "table"
}
-- 5 total entries.
Recursively counts the total number of elements in the given table. Searches all sub tables.
table tbl
- The table to count
Returns the total element count in a recursive table
mytbl = gb.RandomTable( 10 )
{
[1] = Vector (-0.82562392950058, 0.5458727478981 , 0.39729726314545),
[2] = Vector ( 0.79435861110687, -0.79544585943222, -0.37145271897316),
[3] = "hqqmcspyaicboubkwqdesfaxjlscuvlpsgezuml",
[4] = Entity (105) --[[ viewmodel ]],
[5] = Color ( 3, 51, 69, 255) --[[ █ ]],
[6] = "hrnmlwcjfozjkjvcurxjmcrunddoidlgqzcdtoekeu",
[7] = "ypdcqddrskj",
[8] = { --[[ table: 0xee9a3b2a ]] },
[9] = 0
}
-- 9 total entries.
mytbl[8]
{
[1] = true,
[2] = Angle (-230, 286, 128)
}
-- 2 total entries.
gb.CountRecursive( mytbl )
10
Gangbox offers a couple of functions that let you split large chunks of work up over multiple ticks, keeping the game from freezing while you're working.
Run the given function on every element in the table asynchronously.
function func
- The work function to run on each element. Takes two parameters:
any index
: The index of an element in the table.any element
: The value of an element in the table.
- The work function to run on each element. Takes two parameters:
table tbl
- The table with data to run the worker function on
Generates a cache of functions in _R
mapped to the file and line they're defined in. Would normally freeze the game, but threading the work means it can operate over a longer timeframe, only using a small amount of the available tick interval.
nameCache = {}
local worker = function( k, item )
if not isfunction( item ) then return end
local info = debug.getinfo( item )
local sourceFile = info.short_src
if sourceFile == "[C]" then return end
local line = info.linedefined
nameCache[item] = sourceFile .. ":" .. line
end
gb.ThreadWork( worker, _R )
Output:
Thread progress: 19%
Thread progress: 39%
Thread progress: 59%
Thread progress: 79%
Thread progress: 99%
Thread progress: 100%
PrintTable( nameCache )
function: 0xb5ba2b6a = addons/glib/lua/glib/resources/resources.lua:196
function: 0xb5bb9ff2 = addons/glib/lua/glib/transfers/transfers.lua:200
function: 0xbce91832 = gamemodes/base/gamemode/cl_voice.lua:107
function: 0xbcec3caa = lua/autorun/client/toolsearch.lua:129
function: 0xbdac5d6a = lua/vgui/dcheckbox.lua:56
function: 0xbe9e6f7a = lua/vgui/dmenu.lua:136
function: 0xbe9f9f7a = addons/niknaks/lua/niknaks/modules/sh_datetime.lua:56
function: 0xbea8edf2 = lua/cw/shared/cw_cmodel_management.lua:30
function: 0xbead5c6a = addons/mat_faker/lua/missing_no_more/client/progress.lua:52
function: 0xbeaf0e7a = lua/mediaplayer/sh_mediaplayer.lua:231
function: 0xbf9a58a2 = lua/autorun/client/toolsearch.lua:129
function: 0xbf9afa2a = lua/autorun/client/toolsearch.lua:129
function: 0xbf9b982a = lua/vgui/dcategorylist.lua:30
function: 0xbf9b9972 = lua/autorun/client/toolsearch.lua:129
function: 0xbf9ed9aa = lua/derma/init.lua:56
function: 0xbfa83b72 = lua/derma/init.lua:56
function: 0xbfa918f2 = lua/tfa/modules/cl_tfa_models.lua:3
function: 0xbfa9bc7a = lua/derma/init.lua:56
function: 0xbfaa6d62 = lua/vgui/dimage.lua:149
function: 0xbfacadfa = addons/ulib/lua/ulib/shared/hook.lua:97
function: 0xbfbabe32 = lua/autorun/client/toolsearch.lua:129
function: 0xbfbbaf2a = lua/autorun/client/toolsearch.lua:129
function: 0xbfceba72 = lua/autorun/client/toolsearch.lua:129
function: 0xbfcebd2a = lua/derma/init.lua:56
function: 0xc4898a22 = lua/autorun/client/toolsearch.lua:129
function: 0xc48d09aa = lua/vgui/dcheckbox.lua:162
function: 0xc48d2dfa = addons/cfc_wire/lua/entities/gmod_wire_egp/lib/egplib/queuesystem.lua:130
function: 0xc48ed962 = lua/vgui/dcategorycollapse.lua:166
function: 0xc48fbd22 = lua/autorun/client/toolsearch.lua:129
function: 0xc49cffaa = lua/vgui/dtree_node.lua:553
...
This one is cool. Give gb.Thread
any function that runs work in a for loop (pairs
or ipairs
), and the work will be run asynchronously instead, with no code changes required to the base function.
This is great if you need to run huge functions from another addon where you can't change the source, but you don't want it to freeze the game.
function func
- The worker function. Takes no parameters.
Same example as before, generating a source cache for all functions in _R
, but this time, the function is defined by itself, and we Thread it after it's instantiated.
nameCache = {}
local worker = function()
for _, item in ipairs( _R ) do
if not isfunction( item ) then return end
local info = debug.getinfo( item )
local sourceFile = info.short_src
if sourceFile == "[C]" then return end
local line = info.linedefined
nameCache[item] = sourceFile .. ":" .. line
end
end
gb.Thread( worker )
Output:
Thread progress: 19%
Thread progress: 39%
Thread progress: 59%
Thread progress: 79%
Thread progress: 99%
Thread progress: 100%
PrintTable( nameCache )
function: 0xb5ba2b6a = addons/glib/lua/glib/resources/resources.lua:196
function: 0xb5bb9ff2 = addons/glib/lua/glib/transfers/transfers.lua:200
function: 0xbce91832 = gamemodes/base/gamemode/cl_voice.lua:107
function: 0xbcec3caa = lua/autorun/client/toolsearch.lua:129
function: 0xbdac5d6a = lua/vgui/dcheckbox.lua:56
function: 0xbe9e6f7a = lua/vgui/dmenu.lua:136
function: 0xbe9f9f7a = addons/niknaks/lua/niknaks/modules/sh_datetime.lua:56
function: 0xbea8edf2 = lua/cw/shared/cw_cmodel_management.lua:30
function: 0xbead5c6a = addons/mat_faker/lua/missing_no_more/client/progress.lua:52
function: 0xbeaf0e7a = lua/mediaplayer/sh_mediaplayer.lua:231
function: 0xbf9a58a2 = lua/autorun/client/toolsearch.lua:129
function: 0xbf9afa2a = lua/autorun/client/toolsearch.lua:129
function: 0xbf9b982a = lua/vgui/dcategorylist.lua:30
function: 0xbf9b9972 = lua/autorun/client/toolsearch.lua:129
function: 0xbf9ed9aa = lua/derma/init.lua:56
function: 0xbfa83b72 = lua/derma/init.lua:56
function: 0xbfa918f2 = lua/tfa/modules/cl_tfa_models.lua:3
function: 0xbfa9bc7a = lua/derma/init.lua:56
function: 0xbfaa6d62 = lua/vgui/dimage.lua:149
function: 0xbfacadfa = addons/ulib/lua/ulib/shared/hook.lua:97
function: 0xbfbabe32 = lua/autorun/client/toolsearch.lua:129
function: 0xbfbbaf2a = lua/autorun/client/toolsearch.lua:129
function: 0xbfceba72 = lua/autorun/client/toolsearch.lua:129
function: 0xbfcebd2a = lua/derma/init.lua:56
function: 0xc4898a22 = lua/autorun/client/toolsearch.lua:129
function: 0xc48d09aa = lua/vgui/dcheckbox.lua:162
function: 0xc48d2dfa = addons/cfc_wire/lua/entities/gmod_wire_egp/lib/egplib/queuesystem.lua:130
function: 0xc48ed962 = lua/vgui/dcategorycollapse.lua:166
function: 0xc48fbd22 = lua/autorun/client/toolsearch.lua:129
function: 0xc49cffaa = lua/vgui/dtree_node.lua:553
...