NetworkManager.Bots.cs
using System;
namespace Sandbox;
public sealed partial class NetworkManager
{
/// <summary>Number of bots in addition to human players. Set to zero to disable bots.</summary>
[ConVar( "bot_count", ConVarFlags.GameSetting | ConVarFlags.Replicated ), Range( 0, 32 ), Step( 1 )]
public static int BotCount { get; set; } = 4;
private float _nextBotUpdate;
private int _nextBotNumber = 1;
private void UpdateBots()
{
if ( !Networking.IsHost || Time.Now < _nextBotUpdate )
return;
_nextBotUpdate = Time.Now + 0.5f;
var sessions = PlayerSessions.Where( s => s.IsValid() && s.IsBot ).ToList();
var desiredCount = Math.Clamp( BotCount, 0, 32 );
while ( sessions.Count > desiredCount )
{
var session = sessions[^1];
DestroyBotPawns( session.BotId );
session.GameObject.Destroy();
sessions.RemoveAt( sessions.Count - 1 );
}
// Map loading and navmesh generation finish independently. Wait until
// navigation is ready for the bots to move after spawning.
if ( !IsMapReady || !Scene.NavMesh.IsEnabled || Scene.NavMesh.IsGenerating )
return;
if ( !PlayerPrefab.IsValid() || DeathmatchGame.Instance is not { State: MatchState.IN_PROGRESS } )
return;
while ( sessions.Count < desiredCount )
{
var sessionObject = new GameObject( false, $"BotSession_{_nextBotNumber}" );
var session = sessionObject.AddComponent<PlayerSession>();
session.BotId = Guid.NewGuid();
session.DisplayName = $"Bot {_nextBotNumber++}";
session.IsReadyToSpawn = true;
sessionObject.NetworkSpawn( new NetworkSpawnOptions
{
Owner = Connection.Host,
OwnerTransfer = OwnerTransfer.Fixed,
StartEnabled = true
} );
sessions.Add( session );
TrySpawnPlayer( session );
}
}
private void DestroyBotPawns( Guid botId = default )
{
if ( !Networking.IsHost )
return;
foreach ( var bot in Scene.GetAll<Player>().Where( p => p.IsBot && (botId == Guid.Empty || p.BotId == botId) ).ToArray() )
bot.GameObject.Destroy();
}
}