Discord  ·  Support Development  ·  ApolloSource  ·  MightyX3N
Early Access · Beta

CSS-Style Bots for
Garry's Mod

A standalone bot addon with navmesh AI, Red vs Blue teamplay, configurable loadouts, an admin menu with 3D previews, and a comprehensive hook API for gamemode integration.

38
Hooks
22
ConVars
4
Difficulty Levels
Any
Gamemode
01

Features

Combat AI

Navmesh pathfinding, target acquisition, weapon switching, reloading, and obstruction handling across four difficulty levels.

Red vs Blue

Full team mode with join screen, force-team, auto-balance, and team-aligned NPC relationships.

Loadout System

Named weapon loadouts scoped to Global, Red, or Blue. Bots randomly pick an enabled loadout at spawn.

Hook API

38 serverside hooks covering creation, targeting, movement, damage, freezing, waypoints, and more.

Admin Menu

In-game GUI with ConVar controls, loadout editor with live 3D weapon preview, playermodel viewer, and per-bot management.

Per-Bot Freeze

Freeze individual bots with bot_freeze <name> or via the Bots tab, independent of the global bot_stop.

Health & Armour

Set custom starting health and armour via bot_health and bot_armor ConVars. Persists across saves.

TFA Compatible

Bundled TFA patch silences viewmodel errors. Bots carry and fire TFA weapons without server crashes.

Save / Load

All settings, loadouts, and playermodels persist to disk. Auto-load on every map start with bot_autoload.

02

ConVar Reference

All ConVars are admin-only. Configure them visually with bot_menu and persist changes with bot_data_save.

Bot Management

bot_quota0Max bots. 0 = no cap. Fill with bot_add_fill.
bot_difficulty10=Easy 1=Normal 2=Hard 3=Expert
bot_stop0Freeze all bots globally.
bot_mimic0Bots copy the first human player's movement.
bot_autoadd0Fill to quota whenever a human joins.
bot_autoload0Load saved settings on every map start.
bot_health100Starting health. 0 = gamemode default.
bot_armor0Starting armour. 0 = none.

Behaviour

bot_teamplay0Enable Red (10) vs Blue (11) team mode.
mp_autoteambalance1Balance teams when bots are added.
bot_force_team0Force all bots to a team: 0=auto 10=Red 11=Blue
bot_ignore_player0Bots and NPCs ignore human players.
bot_realistic_movement1Limit speed for better navmesh navigation.
bot_move_speed0Override forward move speed. 0 = defaults.
bot_limitammo1Prevent bots picking up ammo above max.
bot_generatenav0Auto-generate navmesh if map has none.
bot_waypoint_notify0Enable GBots_OnBotWaypoint hook.
Commands: bot_add  ·  bot_add_red  ·  bot_add_blue  ·  bot_add_fill  ·  bot_kick [name]  ·  bot_kill [name|all]  ·  bot_place  ·  bot_freeze <name>  ·  bot_unfreeze <name>  ·  bot_freeze_all  ·  bot_unfreeze_all  ·  bot_freeze_list  ·  bot_data_save  ·  bot_data_load  ·  bot_data_delete  ·  bot_menu
03

Hook Reference

All hooks are serverside. Register with hook.Add( "HookName", "MyAddon_Tag", function(...) end ). Return values override default behaviour where documented. NEW = added in the enhanced update.
Bot Lifecycle
HookArgumentsReturnsDescription
GBots_CanBotBeCreatedteamidfalse to blockReturn false to prevent bot creation. Useful for custom quota or mode restrictions.
GBots_OnBotCreatedbot, teamidFired after a bot entity is created and added to the bot list.
GBots_OnBotKickedbotFired just before a bot is kicked from the server.
GBots_OnBotSpawnedbotFired after a bot spawns or respawns and receives its loadout.
GBots_OnBotDeathbot, attackerFired when a bot dies. Attacker may be nil.
GBots_OnBotDamagedbot, dmgfalse to suppressFired on damage. Return false to prevent automatic target-set reaction.
GBots_GetRespawnDelaybotnumber (seconds)Override how long a bot waits before respawning. Default 1 second.
GBots_OnTeamAssignedbot, teamidFired when a bot is assigned to Red or Blue team.
GBots_OnBotTeamKillNEWbot, victimFired when a bot kills a teammate in teamplay mode.
GBots_OnBotFrozenNEWbot, isFrozenFired when a bot is individually frozen or unfrozen via bot_freeze / bot_unfreeze.
Spawn & Loadout
HookArgumentsReturnsDescription
GBots_GetSpawnPointbot, teamidEntityReturn a spawn point entity to override the default picker.
GBots_GetBotNameteamidstringReturn a string to override the name assigned to a new bot.
GBots_GetPlayerModelbot, teamidstring (model path)Return a model path to override the playermodel on spawn.
GBots_GetLoadoutbot, teamidtable of classnamesReturn a weapon class table to completely override the loadout system.
GBots_OnLoadoutGivenbot, weaponsFired after weapons are given. weapons is the table that was used.
Targeting & Combat
HookArgumentsReturnsDescription
GBots_CanTargetbot, targetfalse to blockReturn false to prevent this bot from targeting a specific entity.
GBots_OnTargetFoundbot, targetFired when a bot acquires a new target.
GBots_OnTargetLostbot, targetFired when a bot loses sight of its current target.
GBots_CanAttackbot, targetfalse to blockReturn false to prevent a bot from firing at its target this tick.
GBots_OnBotAttackNEWbot, target, weaponFired each time a bot fires at its target. Includes the active weapon entity.
GBots_OnBotReloadNEWbot, weaponFired when a bot begins reloading a weapon.
GBots_OnBotSwitchWeaponNEWbot, oldWep, newWepFired when a bot selects a new active weapon. oldWep may be invalid.
GBots_OnWeaponDropbot, weaponFired just before a bot drops a weapon that has run out of ammo.
GBots_GetNPCDispositionbot, npcD_* constantReturn D_HT, D_LI, or D_NU to override NPC-to-bot relationships.
GBots_OnObstructionFoundbot, ent, isDoorfalse to ignoreFired when a blocking prop or door is detected. Return false to skip attacking it.
GBots_CanPickupItemNEWbot, itemfalse to blockReturn false to stop a bot from pathing toward a specific item or weapon pickup.
Movement & Navigation
HookArgumentsReturnsDescription
GBots_CanMoveNEWbotfalse to freezeReturn false each tick to freeze this specific bot in place.
GBots_CanRoamNEWbotfalse to stopReturn false to prevent a bot from roaming when it has no target.
GBots_GetMoveSpeedNEWbotnumberReturn a forward move speed value to override this bot's movement this tick.
GBots_CanJumpbotfalse to blockReturn false to prevent the bot from jumping on navmesh jump links.
GBots_CanCrouchNEWbotfalse to blockReturn false to prevent the bot from crouching even in NAV_MESH_CROUCH areas.
GBots_GetRoamTargetbotVector or EntityReturn a Vector or Entity to guide roaming instead of a random navmesh area.
GBots_OnPathComputedbot, pathFired after a new navmesh path is computed while chasing a target.
GBots_OnBotStuckbotFired when stuck detection triggers and the path is invalidated.
GBots_OnBotWaypointNEWbot, posFired when a bot reaches its path goal. Requires bot_waypoint_notify 1.
Admin & Config
HookArgumentsReturnsDescription
GBots_OnMenuOpenedplyFired when an admin opens the bot menu.
GBots_OnSettingsSavedFired after all settings are written to disk.
GBots_OnSettingsLoadedFired after settings are read from disk.
Global access: The bot system is exposed as the serverside global GBots. Use GBots.List to iterate all active bots. Set GBots.Frozen[bot] = true to freeze a bot programmatically.
04

Creating a Custom Addon

1

Create your server autorun file

G-Bots hooks are serverside. Create a file under lua/autorun/server/ in your addon folder.

addons/myaddon/lua/autorun/server/sv_myaddon.lua
-- G-Bots integration for MyAddon
2

Guard against G-Bots not being installed

The global GBOT_TEAM_RED only exists when G-Bots is loaded. Always check it before registering hooks.

if not GBOT_TEAM_RED then return end
3

Register hooks with a unique tag

Always use your addon name as the tag to avoid conflicts with other addons using the same hook.

hook.Add( "GBots_OnBotCreated", "MyAddon_BotCreated", function( bot, teamid ) -- bot is the player entity (NextBot) -- teamid is GBOT_TEAM_RED, GBOT_TEAM_BLUE, or nil bot:SetNWString( "MyAddon_Role", "soldier" ) end )
4

Use return values to override behaviour

Hooks documented with a Returns value will use your return to change G-Bots behaviour. Return nil or nothing to use the default.

hook.Add( "GBots_GetLoadout", "MyAddon_Loadout", function( bot, teamid ) if bot:GetNWString( "MyAddon_Role" ) == "sniper" then return { "weapon_ar2", "weapon_pistol" } end -- nil here = use G-Bots loadout system normally end )
5

Access the bot list and freeze table directly

The GBots global gives you direct programmatic control over all bots.

-- Iterate all active bots for _, bot in ipairs( GBots.List ) do if IsValid( bot ) then print( bot:Nick(), bot:Health() ) end end -- Freeze / unfreeze a bot from code GBots.Frozen[ someBotEntity ] = true -- freeze GBots.Frozen[ someBotEntity ] = nil -- unfreeze
05

Example Addons

Real patterns you can copy and adapt. All examples guard against G-Bots not being installed.

Bot Score Tracker

Lifecycle
Track kills and deaths per bot, and announce the leader on each kill.
if not GBOT_TEAM_RED then return end local scores = {} hook.Add( "GBots_OnBotCreated", "Score_Init", function( bot ) scores[ bot:EntIndex() ] = { kills=0, deaths=0, name=bot:Nick() } end ) hook.Add( "GBots_OnBotDeath", "Score_Death", function( bot, attacker ) local s = scores[ bot:EntIndex() ] if s then s.deaths = s.deaths + 1 end if IsValid( attacker ) and attacker:IsBot() then local as = scores[ attacker:EntIndex() ] if as then as.kills = as.kills + 1 PrintMessage( HUD_PRINTCENTER, as.name .. " leads: " .. as.kills .. " kills" ) end end end )

Friendly Escort Bots

TargetingMovement
Block targeting of humans and make bots follow the nearest player instead.
if not GBOT_TEAM_RED then return end hook.Add( "GBots_CanTarget", "Escort_NoTarget", function( bot, target ) if target:IsPlayer() and not target:IsBot() then return false end end ) hook.Add( "GBots_GetRoamTarget", "Escort_Follow", function( bot ) local nearest, dist for _, ply in ipairs( player.GetHumans() ) do if ply:Alive() then local d = bot:GetPos():DistToSqr( ply:GetPos() ) if not dist or d < dist then nearest=ply dist=d end end end return nearest end )

Wave Survival Mode

LifecycleConfig
No respawning. Track all bots dying to trigger the next wave with increased difficulty.
if not GBOT_TEAM_RED then return end local wave=1 local alive=0 hook.Add( "GBots_OnBotCreated", "Wave_Count", function() alive=alive+1 end ) hook.Add( "GBots_GetRespawnDelay", "Wave_NoRespawn", function() return 99999 -- effectively disables respawning end ) hook.Add( "GBots_OnBotDeath", "Wave_Check", function() alive = alive - 1 if alive <= 0 then wave = wave + 1 RunConsoleCommand( "bot_difficulty", math.min( wave-1, 3 ) ) PrintMessage( HUD_PRINTCENTER, "Wave " .. wave .. " incoming!" ) timer.Simple( 3, function() RunConsoleCommand( "bot_add_fill" ) end ) end end )

DarkRP Job Integration

Loadout
Give bots weapons from DarkRP job tables and assign job-specific names by team.
if not GBOT_TEAM_RED then return end hook.Add( "GBots_GetLoadout", "DRP_Loadout", function( bot, teamid ) local jobKey = teamid == GBOT_TEAM_RED and "police" or "thief" local job = RPExtraTeams[ jobKey ] if not job then return end local weps = {} for _, w in ipairs( job.weapons or {} ) do weps[#weps+1]=w end return #weps > 0 and weps or nil end ) hook.Add( "GBots_GetBotName", "DRP_Name", function( teamid ) if teamid == GBOT_TEAM_RED then return "Officer" end if teamid == GBOT_TEAM_BLUE then return "Criminal" end end )

Sentry / Guard Post Bots

NEWMovement
Make named bots stand perfectly still at their spawn position using the new CanMove and CanRoam hooks.
if not GBOT_TEAM_RED then return end local posts = {} -- bot entity -> spawn Vector hook.Add( "GBots_OnBotSpawned", "Sentry_Assign", function( bot ) if string.find( bot:Nick(), "Guard" ) then posts[ bot ] = bot:GetPos() end end ) -- Freeze movement every tick for sentry bots hook.Add( "GBots_CanMove", "Sentry_Freeze", function( bot ) if posts[ bot ] then return false end end ) -- Also block random roaming hook.Add( "GBots_CanRoam", "Sentry_NoRoam", function( bot ) if posts[ bot ] then return false end end )

Waypoint Patrol Route

NEWMovement
Combine GetRoamTarget and OnBotWaypoint to walk bots around a fixed patrol loop.
if not GBOT_TEAM_RED then return end RunConsoleCommand( "bot_waypoint_notify", "1" ) local route = { Vector( 100, 200, 0 ), Vector( 400, 200, 0 ), Vector( 400, 600, 0 ), Vector( 100, 600, 0 ), } local wpIdx = {} -- bot -> current route index hook.Add( "GBots_GetRoamTarget", "Patrol_Target", function( bot ) return route[ wpIdx[ bot ] or 1 ] end ) hook.Add( "GBots_OnBotWaypoint", "Patrol_Advance", function( bot ) wpIdx[ bot ] = ( ( wpIdx[ bot ] or 1 ) % #route ) + 1 end )

Friendly Fire Logger

NEWLifecycle
Log team kills and auto-kick bots that repeatedly shoot teammates.
if not GBOT_TEAM_RED then return end local ffCounts = {} hook.Add( "GBots_OnBotTeamKill", "FF_Log", function( bot, victim ) local key = bot:EntIndex() ffCounts[ key ] = ( ffCounts[ key ] or 0 ) + 1 PrintMessage( HUD_PRINTTALK, "[FF] " .. bot:Nick() .. " killed teammate " .. victim:Nick() .. " (" .. ffCounts[ key ] .. "x)" ) if ffCounts[ key ] >= 3 then RunConsoleCommand( "bot_kick", bot:Nick() ) end end )

Item Pickup Restriction

NEWLoadout
Block bots from picking up health or weapons from the ground, forcing loadout-only play.
if not GBOT_TEAM_RED then return end hook.Add( "GBots_CanPickupItem", "NoPickup", function( bot, item ) local class = item:GetClass() -- Block all health pickups if string.find( class, "health" ) then return false end -- Block Blue team from picking up ground weapons if bot:Team() == GBOT_TEAM_BLUE and item:IsWeapon() then return false end end )