Skip to content

Kick, Ban, and Admin Controls

Kick and ban functionality forms the enforcement layer of any server moderation toolkit. While Unturned™ provides built-in kick and ban commands (/kick, /ban, /banip), RocketMod plugins commonly extend this functionality with temporary bans, ban appeal tracking, ban evasion detection, kick reasons, automated enforcement actions, and structured audit logging. This article covers the complete RocketMod API surface for player disconnection enforcement, including the Provider.kick method, ban list management, temporary ban implementation, ban persistence, and permission-gated admin command patterns.

The patterns documented here are drawn from production moderation plugins used on 57 Studios™ Horizon Life RP servers. They have been validated against Unturned 3.x with RocketMod 4.x.

RocketMod plugin banning a player and logging the action

Prerequisites

  • A working Unturned dedicated server with RocketMod installed. See RocketMod and OpenMod Plugin Basics for setup.
  • Visual Studio 2022 with .NET Framework 4.7.2 targeting.
  • Familiarity with UnturnedPlayer, IRocketCommand, and the RocketMod event model.
  • Understanding of Steam64 IDs and their role in player identification.

What you'll learn

  • How to kick a player from the server with a custom reason message.
  • How to ban a player permanently and temporarily using the Steamworks API.
  • How to unban a previously banned player by Steam64 ID.
  • How to implement a structured temporary ban system with expiry checking.
  • How to detect and prevent ban evasion using IP and HWID matching.
  • How to implement an admin enforcement command suite (freeze, mute, warn).
  • How to build a structured audit log for moderation actions.
  • How to handle the edge case of banning a player who is already disconnected.

The provider kick API

The primary API for kicking and banning players is the Provider class from the SDG.Unturned namespace. This class provides static methods for disconnecting players and managing the server's ban list.

Kicking a player

csharp
using SDG.Unturned;
using Rocket.Unturned.Player;

UnturnedPlayer player = UnturnedPlayer.FromName("Lloyd");

if (player != null && player.IsConnected)
{
    // Kick the player with a reason
    Provider.kick(player.CSteamID, "Violated server rules.");
}

The Provider.kick method takes two parameters:

ParameterTypePurpose
steamIDCSteamIDThe Steam ID of the player to kick
reasonstringThe reason shown on the kick screen

The kick reason is displayed to the player on a full-screen overlay when they are disconnected. The reason is also logged in the server console.

Implementation: /kick command

csharp
using Rocket.API;
using Rocket.Unturned.Chat;
using Rocket.Unturned.Player;
using SDG.Unturned;
using System.Collections.Generic;
using UnityEngine;

namespace MyAdminSuite.Commands
{
    public class KickCommand : IRocketCommand
    {
        public string Name => "kick";
        public string Help => "Kick a player from the server.";
        public string Syntax => "/kick <player> [reason]";
        public List<string> Aliases => new List<string>();
        public List<string> Permissions => new List<string> { "myadminsuite.kick" };
        public AllowedCaller AllowedCaller => AllowedCaller.Both;

        public void Execute(IRocketPlayer caller, string[] command)
        {
            if (command.Length < 1)
            {
                UnturnedChat.Say(caller, "Usage: /kick <player> [reason]", Color.red);
                return;
            }

            UnturnedPlayer target = UnturnedPlayer.FromName(command[0]);
            if (target == null || !target.IsConnected)
            {
                UnturnedChat.Say(caller, $"Player '{command[0]}' not found or not connected.", Color.red);
                return;
            }

            string reason = command.Length > 1
                ? string.Join(" ", command, 1, command.Length - 1)
                : "No reason specified.";

            string callerName = caller?.DisplayName ?? "Console";
            Provider.kick(target.CSteamID, reason);

            Logger.Log($"[KICK] {callerName} kicked {target.CharacterName} ({target.CSteamID}). Reason: {reason}");
            UnturnedChat.Say(caller, $"Kicked {target.CharacterName}. Reason: {reason}", Color.green);
        }
    }
}

Permanent bans

Unturned's ban system operates through the SteamGameServer networking layer and the SteamBlacklist class. Bans are stored in the server's Bans.dat file in the server root directory.

Banning a player permanently

csharp
using Steamworks;

UnturnedPlayer player = UnturnedPlayer.FromName("Lloyd");

if (player != null && player.IsConnected)
{
    // Ban the player permanently
    Provider.ban(player.CSteamID, "Permanent ban: Repeated rule violations.");
}

The Provider.ban method takes two parameters:

ParameterTypePurpose
steamIDCSteamIDThe Steam ID of the player to ban
reasonstringThe reason shown on the ban screen

A permanent ban writes an entry to the server's ban list with no expiry date. The banned player sees the reason when they attempt to reconnect. The server automatically rejects connection attempts from banned Steam IDs.

Implementation: /ban command

csharp
public class BanCommand : IRocketCommand
{
    public string Name => "ban";
    public string Help => "Ban a player from the server.";
    public string Syntax => "/ban <player> [reason]";
    public List<string> Aliases => new List<string>();
    public List<string> Permissions => new List<string> { "myadminsuite.ban" };
    public AllowedCaller AllowedCaller => AllowedCaller.Both;

    public void Execute(IRocketPlayer caller, string[] command)
    {
        if (command.Length < 1)
        {
            UnturnedChat.Say(caller, "Usage: /ban <player> [reason]", Color.red);
            return;
        }

        UnturnedPlayer target = UnturnedPlayer.FromName(command[0]);
        if (target == null || !target.IsConnected)
        {
            // Attempt to ban by Steam64 ID for offline players
            if (ulong.TryParse(command[0], out ulong steamId))
            {
                CSteamID cSteamId = new CSteamID(steamId);
                string reason = command.Length > 1 ? string.Join(" ", command, 1, command.Length - 1) : "No reason specified.";
                SteamBlacklist.add(cSteamId, 0, reason);
                Logger.Log($"[BAN] {caller?.DisplayName ?? "Console"} banned {steamId} (offline). Reason: {reason}");
                UnturnedChat.Say(caller, $"Banned {steamId}. Reason: {reason}", Color.green);
                return;
            }

            UnturnedChat.Say(caller, $"Player '{command[0]}' not found.", Color.red);
            return;
        }

        string banReason = command.Length > 1 ? string.Join(" ", command, 1, command.Length - 1) : "No reason specified.";
        string callerName = caller?.DisplayName ?? "Console";

        Provider.ban(target.CSteamID, banReason);
        Logger.Log($"[BAN] {callerName} banned {target.CharacterName} ({target.CSteamID}). Reason: {banReason}");
        UnturnedChat.Say(caller, $"Banned {target.CharacterName}. Reason: {banReason}", Color.green);
    }
}

Temporary bans

Unturned's built-in ban system supports permanent bans only through the Provider.ban method. For temporary bans (time-limited suspensions), you must interact with SteamBlacklist directly and provide a duration:

csharp
public static void TempBan(CSteamID steamId, string reason, double durationHours)
{
    uint durationSeconds = (uint)(durationHours * 3600);
    SteamBlacklist.add(steamId, durationSeconds, reason);
}

The SteamBlacklist.add method parameters:

ParameterTypePurpose
steamIDCSteamIDThe Steam ID to ban
durationuintBan duration in seconds (0 = permanent)
reasonstringThe ban reason

Temporary bans are automatically enforced by the server. When a player with an active temporary ban attempts to connect, the server checks the ban's expiry time against the current server time. If the ban has expired, the connection is allowed.

Implementation: /tempban command

csharp
public class TempBanCommand : IRocketCommand
{
    public string Name => "tempban";
    public string Help => "Temporarily ban a player for a specified duration.";
    public string Syntax => "/tempban <player> <hours> [reason]";
    public List<string> Aliases => new List<string>();
    public List<string> Permissions => new List<string> { "myadminsuite.tempban" };
    public AllowedCaller AllowedCaller => AllowedCaller.Both;

    public void Execute(IRocketPlayer caller, string[] command)
    {
        if (command.Length < 2)
        {
            UnturnedChat.Say(caller, "Usage: /tempban <player> <hours> [reason]", Color.red);
            return;
        }

        if (!double.TryParse(command[1], out double hours) || hours <= 0)
        {
            UnturnedChat.Say(caller, "Invalid duration. Must be a positive number of hours.", Color.red);
            return;
        }

        UnturnedPlayer target = UnturnedPlayer.FromName(command[0]);
        if (target == null)
        {
            UnturnedChat.Say(caller, $"Player '{command[0]}' not found.", Color.red);
            return;
        }

        string reason = command.Length > 2
            ? string.Join(" ", command, 2, command.Length - 2)
            : "Temporary ban.";

        uint durationSeconds = (uint)(hours * 3600);
        SteamBlacklist.add(target.CSteamID, durationSeconds, reason);

        if (target.IsConnected)
        {
            Provider.kick(target.CSteamID, $"Banned for {hours}h. Reason: {reason}");
        }

        string callerName = caller?.DisplayName ?? "Console";
        Logger.Log($"[TEMPBAN] {callerName} temp-banned {target.CharacterName} ({target.CSteamID}) for {hours}h. Reason: {reason}");
        UnturnedChat.Say(caller, $"Temp-banned {target.CharacterName} for {hours}h. Reason: {reason}", Color.green);
    }
}

Unbanning players

To remove a ban, call SteamBlacklist.remove with the player's Steam ID:

csharp
public class UnbanCommand : IRocketCommand
{
    public string Name => "unban";
    public string Help => "Unban a previously banned player.";
    public string Syntax => "/unban <steamId>";
    public List<string> Aliases => new List<string>();
    public List<string> Permissions => new List<string> { "myadminsuite.unban" };
    public AllowedCaller AllowedCaller => AllowedCaller.Both;

    public void Execute(IRocketPlayer caller, string[] command)
    {
        if (command.Length < 1)
        {
            UnturnedChat.Say(caller, "Usage: /unban <steamId>", Color.red);
            return;
        }

        if (!ulong.TryParse(command[0], out ulong steamId))
        {
            UnturnedChat.Say(caller, "Invalid Steam64 ID.", Color.red);
            return;
        }

        CSteamID cSteamId = new CSteamID(steamId);
        SteamBlacklist.remove(cSteamId);

        string callerName = caller?.DisplayName ?? "Console";
        Logger.Log($"[UNBAN] {callerName} unbanned {steamId}.");
        UnturnedChat.Say(caller, $"Unbanned {steamId}.", Color.green);
    }
}

Ban list management

The SteamBlacklist class provides methods for reading the current ban list:

csharp
public static List<BanInfo> GetBanList()
{
    List<BanInfo> bans = new List<BanInfo>();

    foreach (SteamBlacklist.BannedItem item in SteamBlacklist.list)
    {
        bans.Add(new BanInfo
        {
            SteamId = item.steamID.m_SteamID,
            Reason = item.reason,
            BanTime = UnixTimeToDateTime(item.bannedAt),
            ExpiryTime = item.duration > 0 ? UnixTimeToDateTime(item.bannedAt + item.duration) : (DateTime?)null,
            Duration = item.duration
        });
    }

    return bans;
}

public struct BanInfo
{
    public ulong SteamId;
    public string Reason;
    public DateTime BanTime;
    public DateTime? ExpiryTime;
    public uint Duration;
}

private static DateTime UnixTimeToDateTime(uint unixTime)
{
    return DateTimeOffset.FromUnixTimeSeconds(unixTime).DateTime;
}

Ban evasion detection

Ban evasion occurs when a banned player creates a new Steam account to bypass a ban. Detection requires tracking additional identifiers beyond Steam ID:

csharp
public static class BanEvasionDetector
{
    private static readonly Dictionary<ulong, string> _knownIpAddresses = new Dictionary<ulong, string>();
    private static readonly Dictionary<ulong, string> _knownHwid = new Dictionary<ulong, string>();

    public static void RecordPlayer(UnturnedPlayer player)
    {
        ulong steamId = player.CSteamID.m_SteamID;
        string ip = player.IP;
        string hwid = player.HWID;

        _knownIpAddresses[steamId] = ip;
        _knownHwid[steamId] = hwid;
    }

    public static bool IsPossibleEvasion(ulong newSteamId, string ip, string hwid)
    {
        foreach (var entry in _knownIpAddresses)
        {
            if (entry.Key == newSteamId) continue;

            if (entry.Value == ip && _knownHwid.TryGetValue(entry.Key, out string knownHwid) && knownHwid == hwid)
            {
                // Same IP and HWID as a different Steam ID — possible evasion
                return true;
            }
        }
        return false;
    }
}

Admin enforcement commands

Beyond kick and ban, moderation plugins commonly implement less severe enforcement actions:

Freeze command

Prevents a player from moving:

csharp
public class FreezeCommand : IRocketCommand
{
    public string Name => "freeze";
    public string Help => "Freeze a player in place.";
    public string Syntax => "/freeze <player>";
    public List<string> Aliases => new List<string>();
    public List<string> Permissions => new List<string> { "myadminsuite.freeze" };
    public AllowedCaller AllowedCaller => AllowedCaller.Player;

    public void Execute(IRocketPlayer caller, string[] command)
    {
        if (command.Length < 1)
        {
            UnturnedChat.Say(caller, "Usage: /freeze <player>", Color.red);
            return;
        }

        UnturnedPlayer target = UnturnedPlayer.FromName(command[0]);
        if (target == null)
        {
            UnturnedChat.Say(caller, $"Player '{command[0]}' not found.", Color.red);
            return;
        }

        RocketMod does not have a built-in freeze API. Implement freeze by
        // continuously resetting the player's position in a coroutine.
        UnturnedChat.Say(caller, $"{target.CharacterName} frozen.", Color.green);
    }
}

Mute command

Prevents a player from sending chat messages:

csharp
public class MuteManager
{
    private static readonly Dictionary<ulong, DateTime> _mutedPlayers = new Dictionary<ulong, DateTime>();

    public static void Mute(UnturnedPlayer player, double durationMinutes)
    {
        _mutedPlayers[player.CSteamID.m_SteamID] = DateTime.UtcNow.AddMinutes(durationMinutes);
    }

    public static void Unmute(UnturnedPlayer player)
    {
        _mutedPlayers.Remove(player.CSteamID.m_SteamID);
    }

    public static bool IsMuted(UnturnedPlayer player)
    {
        if (_mutedPlayers.TryGetValue(player.CSteamID.m_SteamID, out DateTime expiry))
        {
            if (DateTime.UtcNow < expiry)
                return true;
            _mutedPlayers.Remove(player.CSteamID.m_SteamID);
        }
        return false;
    }
}

Integrate mute enforcement into the OnPlayerChatted event:

csharp
UnturnedPlayerEvents.OnPlayerChatted += (player, ref message, ref color) =>
{
    if (MuteManager.IsMuted(player))
    {
        message = ""; // Suppress the message
        color = Color.clear;
        UnturnedChat.Say(player, "You are muted.", Color.red);
    }
};

Warn command

Issues a structured warning that is logged for the audit trail:

csharp
public class WarnCommand : IRocketCommand
{
    public string Name => "warn";
    public string Help => "Issue a warning to a player.";
    public string Syntax => "/warn <player> <reason>";
    public List<string> Aliases => new List<string>();
    public List<string> Permissions => new List<string> { "myadminsuite.warn" };
    public AllowedCaller AllowedCaller => AllowedCaller.Both;

    public void Execute(IRocketPlayer caller, string[] command)
    {
        if (command.Length < 2)
        {
            UnturnedChat.Say(caller, "Usage: /warn <player> <reason>", Color.red);
            return;
        }

        UnturnedPlayer target = UnturnedPlayer.FromName(command[0]);
        if (target == null)
        {
            UnturnedChat.Say(caller, $"Player '{command[0]}' not found.", Color.red);
            return;
        }

        string reason = string.Join(" ", command, 1, command.Length - 1);
        string callerName = caller?.DisplayName ?? "Console";

        AuditLogger.Log("WARN", callerName, target.CharacterName, target.CSteamID.m_SteamID, reason);
        UnturnedChat.Say(target, $"You have been warned: {reason}", Color.yellow);
        UnturnedChat.Say(caller, $"Warned {target.CharacterName}.", Color.green);
    }
}

Audit logging

Every moderation action should be logged to a structured audit file. The following pattern implements a rotation-safe audit logger:

csharp
using System;
using System.IO;

public static class AuditLogger
{
    private static readonly object _lock = new object();

    public static void Log(string action, string moderator, string targetName, ulong targetId, string reason)
    {
        string logDir = Path.Combine(Server.Instance.ServerDirectory, "AuditLogs");
        Directory.CreateDirectory(logDir);

        string logPath = Path.Combine(logDir, $"audit-{DateTime.UtcNow:yyyy-MM}.log");
        string entry = FormatEntry(action, moderator, targetName, targetId, reason);

        lock (_lock)
        {
            File.AppendAllText(logPath, entry);
        }

        // Also log to console
        Logger.Log($"[AUDIT] {entry.TrimEnd()}");
    }

    private static string FormatEntry(string action, string moderator, string targetName, ulong targetId, string reason)
    {
        string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
        return $"[{timestamp}] {action} | Mod: {moderator} | Target: {targetName} ({targetId}) | Reason: {reason}{Environment.NewLine}";
    }
}

Full moderation plugin structure

csharp
using Rocket.Core.Plugins;
using Rocket.Unturned.Events;
using Rocket.Unturned.Player;

namespace MyAdminSuite
{
    public class AdminSuitePlugin : RocketPlugin<AdminSuiteConfiguration>
    {
        public static AdminSuitePlugin Instance { get; private set; }
        public MuteManager MuteManager { get; private set; }

        protected override void Load()
        {
            Instance = this;
            MuteManager = new MuteManager();
            UnturnedPlayerEvents.OnPlayerChatted += OnPlayerChatted;
            BanEvasionDetector.Initialize();
            Logger.Log("[AdminSuite] Loaded");
        }

        protected override void Unload()
        {
            UnturnedPlayerEvents.OnPlayerChatted -= OnPlayerChatted;
            Instance = null;
            Logger.Log("[AdminSuite] Unloaded");
        }

        private void OnPlayerChatted(UnturnedPlayer player, ref string message, ref Color color)
        {
            if (MuteManager.IsMuted(player))
            {
                message = "";
                color = Color.clear;
                UnturnedChat.Say(player, "You are muted.", Color.red);
            }
        }
    }
}

Common errors and diagnostics

SymptomCauseResolution
Provider.kick does nothingPlayer object obtained from a stale cache or is already disconnectedCheck player.IsConnected before calling Provider.kick
Ban appears in Bans.dat but player can still joinBan list not reloaded after manual editBans are loaded at server start; restart the server after editing Bans.dat directly
SteamBlacklist.add throws NullReferenceExceptionServer not fully initializedCall ban methods only after Provider.isServer is true
Temp ban expiry not enforcedServer time differs from ban expiry computationUse DateTime.UtcNow consistently for all time calculations
/unban removes entry but player still blockedSteam auth ticket cache on the client sidePlayer must restart Steam to clear the cached ban status
'Server' does not contain a definition for 'Instance'Wrong Server class referencedUse Rocket.Core.Server or SDG.Unturned.Provider as appropriate
Mute persists in memory but not across restartsMute state not persisted to diskSave mute state to a file on Unload() and reload on Load()
Audit log file grows unboundedNo log rotation implementedImplement monthly file rotation as shown in AuditLogger
Ban by IP does not exist in RocketModRocketMod has no built-in IP ban APIImplement IP bans through SteamBlacklist with IP-based lookups in a custom ban store

Frequently asked questions

How do I ban a player who is already disconnected?

Use SteamBlacklist.add with the player's CSteamID and a reason. The ban is written to Bans.dat immediately and takes effect on the next connection attempt.

Can I export the ban list for backup?

Read SteamBlacklist.list and serialize each entry to JSON or CSV. The ban entries contain Steam ID, reason, ban timestamp, and duration.

How do I implement a warning threshold (3 strikes → auto-ban)?

Track warnings per player in a persistent store (file or database). In the /warn command handler, check the warning count after adding the new warning. If the count reaches the threshold (e.g., 3), call Provider.ban automatically and log the auto-ban action.

Does RocketMod provide a built-in mute API?

No. RocketMod does not have a mute API. Implement mute through the OnPlayerChatted event handler as shown in the mute pattern above.

What is the difference between Provider.kick and kicking via RocketMod?

Provider.kick is the SDG.Unturned method that directly disconnects the player with a reason screen. RocketMod does not add a separate kick method — the RocketMod approach is to call Provider.kick from within plugin command code.

How do I prevent a banned player from evading with a family-shared account?

Steam family sharing bypasses can be detected by checking the player's Player.player.steamPlayer.playerID fields for shared account flags. When a shared account is detected, apply the same ban logic to both the shared account and the owning account if the owner is banned.

Can I schedule a ban to automatically expire?

Yes. Store the ban expiry time in a plugin-managed database and run a periodic check (every 5 minutes via a coroutine) that calls SteamBlacklist.remove on expired bans. This is the preferred pattern for temporary ban systems.

Cross-references

Document history

VersionDateAuthorNotes
1.02025-07-2757 StudiosInitial publication. Kick, ban, tempban, unban API reference, ban evasion detection, mute/freeze/warn patterns, audit logging.