terminalCode Example

BanSystem Pattern for Player Management

calendar_today May 3, 2026 schedule ~1 min read person patrickjr verified 50

BanSystem - Player Ban Management

A GameObjectSystem that manages player bans with persistent storage. Demonstrates Component.INetworkListener, LocalData persistence, and RPC commands.

Implementation

CSHARP
using Sandbox.UI;

/// <summary>
/// Holds a banlist, can ban users
/// </summary>
public sealed class BanSystem : GameObjectSystem<BanSystem>, Component.INetworkListener
{
    public record struct BanEntry(string DisplayName, string Reason);

    private Dictionary<long, BanEntry> _bans = new();

    public BanSystem(Scene scene) : base(scene)
    {
        // Load bans from local persistent storage
        _bans = LocalData.Get<Dictionary<long, BanEntry>>("bans", new()) ?? new();
    }

    /// <summary>
    /// Called when a player tries to connect. Return false to reject.
    /// </summary>
    bool Component.INetworkListener.AcceptConnection(Connection connection, ref string reason)
    {
        if (!_bans.TryGetValue(connection.SteamId, out var entry))
            return true;

        reason = $"You're banned from this server: {entry.Reason}";
        return false;
    }

    /// <summary>
    /// Bans a connected player and kicks them immediately
    /// </summary>
    public void Ban(Connection connection, string reason)
    {
        Assert.True(Networking.IsHost, "Only the host may ban players.");

        _bans[connection.SteamId] = new BanEntry(connection.DisplayName, reason);
        Save();
        
        Scene.Get<Chat>()?.AddSystemText($"{connection.DisplayName} was banned: {reason}", "🔨");
        connection.Kick(reason);
    }

    /// <summary>
    /// Bans a Steam ID by value. Use for pre-banning or banning offline players.
    /// </summary>
    public void Ban(SteamId steamId, string reason)
    {
        Assert.True(Networking.IsHost, "Only the host may ban players.");

        _bans[steamId] = new BanEntry(steamId.ToString(), reason);
        Save();
    }

    /// <summary>
    /// Removes the ban for the given Steam ID.
    /// </summary>
    public void Unban(SteamId steamId)
    {
        Assert.True(Networking.IsHost, "Only the host may unban players.");

        if (_bans.Remove(steamId))
            Save();
    }

    /// <summary>
    /// Returns true if the given Steam ID is currently banned
    /// </summary>
    public bool IsBanned(SteamId steamId) => _bans.ContainsKey(steamId);

    /// <summary>
    /// Returns a read-only view of all active bans
    /// </summary>
    public IReadOnlyDictionary<SteamId, BanEntry> GetBannedList() => 
        _bans.ToDictionary(x => (SteamId)x.Key, x => x.Value);

    private void Save() => LocalData.Set("bans", _bans);

    /// <summary>
    /// RPC to ban a connected player. Caller must be host or have admin permission.
    /// </summary>
    [Rpc.Host]
    public static void RpcBanPlayer(Connection target, string reason = "Banned")
    {
        if (!Rpc.Caller.HasPermission("admin")) return;

        Current.Ban(target, reason);
    }

    /// <summary>
    /// Bans a player by name or Steam ID. Usage: ban [name|steamid] [reason]
    /// </summary>
    [ConCmd("ban")]
    public static void BanCommand(string target, string reason = "Banned")
    {
        if (!Networking.IsHost) return;

        // Try parsing as a Steam ID (64-bit integer) first
        if (ulong.TryParse(target, out var steamIdValue))
        {
            var steamId = steamIdValue;
            var connection = Connection.All.FirstOrDefault(c => c.SteamId == steamId);

            if (connection is not null)
                Current.Ban(connection, reason);
            else
                Current.Ban(steamId, reason);

            Log.Info($"Banned {steamId}: {reason}");
            return;
        }

        // Fall back to partial name match
        var conn = GameManager.FindPlayerWithName(target);
        if (conn is not null)
        {
            Current.Ban(conn, reason);
            Log.Info($"Banned {conn.DisplayName}: {reason}");
        }
        else
        {
            Log.Warning($"Could not find player '{target}'");
        }
    }
}

Key Patterns

LocalData Persistence

CSHARP
// Load on initialization
_bans = LocalData.Get<Dictionary<long, BanEntry>>("bans", new()) ?? new();

// Save after modifications
private void Save() => LocalData.Set("bans", _bans);

INetworkListener for Connection Control

CSHARP
public sealed class BanSystem : GameObjectSystem<BanSystem>, Component.INetworkListener
{
    bool INetworkListener.AcceptConnection(Connection connection, ref string reason)
    {
        if (IsBanned(connection.SteamId))
        {
            reason = "You're banned from this server";
            return false;
        }
        return true;
    }
}

Permission Checking

CSHARP
[Rpc.Host]
public static void RpcBanPlayer(Connection target, string reason)
{
    // Rpc.Caller is the connection that made the RPC call
    if (!Rpc.Caller.HasPermission("admin")) 
        return;
    
    Current.Ban(target, reason);
}

Connection Management

CSHARP
// Kick a player
connection.Kick(reason);

// Find player by Steam ID
var connection = Connection.All.FirstOrDefault(c => c.SteamId == steamId);

// Find player by name
var conn = GameManager.FindPlayerWithName(name);

Security Considerations

Was this helpful?