using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
///
/// Handles the 'ban' command, allowing admins to ban one or more valid connected players.
///
/// The player issuing the ban command, or null for console.
/// The command information including arguments.
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
ManagePlayersMenu.BanMenu);
return;
}
Ban(caller, player, time, reason, callerName, BanManager, command);
});
}
///
/// Core logic to ban a specific player, scheduling database updates, notifications, and kicks.
///
/// The player issuing the ban, or null for console.
/// The player to be banned.
/// Ban duration in minutes; 0 means permanent.
/// Reason for the ban.
/// Optional caller name string. If null, defaults to player name or console.
/// Optional BanManager to handle ban persistence.
/// Optional command info object for logging.
/// If true, suppresses command logging.
internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle banning logic
Task.Run(async () =>
{
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
});
});
// Determine message keys and arguments based on ban time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_ban_message_time", "sa_admin_ban_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
// Display center message to the player
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Schedule a kick timer
if (player.UserId.HasValue)
{
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED, Config.OtherSettings.KickTime);
}
// Execute ban command if necessary
if (UnlockedCommands)
{
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
}
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_ban {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
}
///
/// Adds a ban for a player by their SteamID, including offline bans.
///
/// The player issuing the ban command.
/// SteamID of the player to ban.
/// Ban duration in minutes (0 means permanent).
/// Reason for banning.
/// Optional ban manager for database operations.
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
}
}
///
/// Handles banning a player by specifying their SteamID via command.
///
/// The player issuing the command, or null if console.
/// Command information including arguments (SteamID, time, reason).
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
var player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
}
Helper.LogCommand(caller, command);
if (UnlockedCommands)
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
}
///
/// Handles banning a player by their IP address, supporting offline banning if player is not online.
///
/// The player issuing the ban command.
/// The command containing the IP, time, and reason arguments.
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: " [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
var ipAddress = command.GetArg(1);
if (!Helper.IsValidIp(ipAddress))
{
command.ReplyToCommand($"Invalid IP address.");
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
var players = Helper.GetPlayerFromIp(ipAddress);
if (players.Count >= 1)
{
foreach (var player in players)
{
if (player == null || !player.IsValid) continue;
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
}
}
else
{
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
await BanManager.AddBanByIp(ipAddress, adminInfo, reason, time);
});
command.ReplyToCommand($"Player with ip {ipAddress} is not online. Ban has been added offline.");
}
Helper.LogCommand(caller, command);
}
///
/// Checks whether the ban duration is valid based on the caller's permissions and configured limits.
///
/// The player issuing the ban command.
/// Requested ban duration in minutes.
/// True if ban duration is valid; otherwise, false.
private bool CheckValidBan(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
if (duration <= 0 && canPermBan == false)
{
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
return false;
}
if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan) return true;
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxBanDuration]}");
return false;
}
///
/// Handles unbanning players by pattern (steamid, name, or IP).
///
/// The player issuing the unban command.
/// Command containing target pattern and optional reason.
[RequiresPermissions("@css/unban")]
[CommandHelper(minArgs: 1, usage: " [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
Task.Run(async () => await BanManager.UnbanPlayer(pattern, callerSteamId, reason));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unbanned player with pattern {pattern}.");
}
///
/// Handles warning players, supporting multiple targets and warning durations.
///
/// The player issuing the warn command.
/// The command containing target, time, and reason parameters.
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null)
return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Warn(caller, player, time, reason, callerName, command);
}
});
}
///
/// Issues a warning penalty to a specific player with optional duration and reason.
///
/// The player issuing the warning.
/// The player to warn.
/// Duration of the warning in minutes.
/// Reason for the warning.
/// Optional display name of the caller.
/// Optional command info for logging.
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
// Freeze player pawn if alive
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
player.PlayerPawn?.Value?.Freeze();
AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle warning logic
Task.Run(async () =>
{
int? penaltyId = await WarnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
punishCommand = value;
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString()));
});
}
}
});
// Determine message keys and arguments based on warning time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_warn_message_perm", "sa_admin_warn_message_perm",
new object[] { reason, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason })
: ("sa_player_warn_message_time", "sa_admin_warn_message_time",
[reason, time, "CALLER"],
["CALLER", player.PlayerName, reason, time]);
// Display center message to the playser
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the warning command
if (command == null)
Helper.LogCommand(caller, $"css_warn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
// Send Discord notification for the warning
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
}
///
/// Adds a warning to a player by their SteamID, including support for offline players.
///
/// The player issuing the warn command.
/// SteamID of the player to warn.
/// Warning duration in minutes.
/// Reason for the warning.
/// Optional warn manager instance.
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Warn(caller, player, time, reason, callerName);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
punishCommand = value;
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
});
}
}
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
}
}
///
/// Handles removing a warning (unwarn) by a pattern string.
///
/// The player issuing the unwarn command.
/// The command containing target pattern.
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
var pattern = command.GetArg(1);
Task.Run(async () => await WarnManager.UnwarnPlayer(pattern));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unwarned player with pattern {pattern}.");
}
}