#include #include #pragma semicolon 1 #pragma newdecls required #define PREFIX "\x01[SM] \x04" // So commands fail with a message instead of an unbound-native error while the core is unloaded. bool g_bCoreLoaded; // Account ids banned this map. This is the ban itself, so leaving and rejoining does not clear it. ArrayList g_alBannedAccounts; // Slot mirror. Also carries bans of unauthenticated clients (account id 0), which last the session only. bool g_bBanned[MAXPLAYERS + 1]; public Plugin myinfo = { name = "Couch Ban", author = "marisakirisame.net", description = "Bans players from the ZE couch until the end of the map", version = "2.0", url = "https://marisakirisame.net" }; // --------------------------------------------------------------------------------------------- // Plugin lifecycle // --------------------------------------------------------------------------------------------- public void OnPluginStart() { LoadTranslations("common.phrases"); // FindTarget's replies. g_alBannedAccounts = new ArrayList(); RegAdminCmd("sm_couchban", Command_CouchBan, ADMFLAG_BAN, "sm_couchban <#userid|name> - bans a player from the couch until the end of the map"); RegAdminCmd("sm_cban", Command_CouchBan, ADMFLAG_BAN, "Alias of sm_couchban"); RegAdminCmd("sm_couchunban", Command_CouchUnban, ADMFLAG_BAN, "sm_couchunban <#userid|name> - lifts a couch ban"); RegAdminCmd("sm_cunban", Command_CouchUnban, ADMFLAG_BAN, "Alias of sm_couchunban"); } public void OnAllPluginsLoaded() { g_bCoreLoaded = LibraryExists("Couch"); // After a reload of this plugin the core still knows who is blocked. if (!g_bCoreLoaded) return; for (int i = 1; i <= MaxClients; i++) { if (!IsClientInGame(i) || !Couch_IsClientBlocked(i)) continue; g_bBanned[i] = true; RememberAccount(i); } } public void OnLibraryAdded(const char[] name) { if (!StrEqual(name, "Couch")) return; g_bCoreLoaded = true; // A core reload wiped its flags. A frame later so its natives are bound. RequestFrame(Frame_ReapplyBans); } public void OnLibraryRemoved(const char[] name) { if (StrEqual(name, "Couch")) g_bCoreLoaded = false; } void Frame_ReapplyBans(any data) { if (!g_bCoreLoaded) return; for (int i = 1; i <= MaxClients; i++) { if (g_bBanned[i] && IsClientInGame(i)) Couch_SetClientBlocked(i, true); } } public void OnMapStart() { for (int i = 1; i <= MAXPLAYERS; i++) g_bBanned[i] = false; } public void OnMapEnd() { g_alBannedAccounts.Clear(); } // The account id stays on the list so a rejoin picks the ban back up. public void OnClientDisconnect(int client) { g_bBanned[client] = false; } // Either "in game" or "authorized" can come last, so both are hooked. public void OnClientPutInServer(int client) { TryReapplyBan(client); } public void OnClientAuthorized(int client, const char[] auth) { TryReapplyBan(client); } void TryReapplyBan(int client) { if (g_bBanned[client] || !IsClientInGame(client)) return; int account = GetSteamAccountID(client); if (account == 0 || g_alBannedAccounts.FindValue(account) == -1) return; g_bBanned[client] = true; if (g_bCoreLoaded) Couch_SetClientBlocked(client, true); PrintToChat(client, PREFIX ... "You are couch-banned\x01 until the end of the map."); } // --------------------------------------------------------------------------------------------- // Commands // --------------------------------------------------------------------------------------------- public Action Command_CouchBan(int client, int args) { if (!RequireCore(client)) return Plugin_Handled; if (args < 1) { ReplyToCommand(client, "[SM] Usage: sm_couchban <#userid|name>"); return Plugin_Handled; } char sArg[MAX_TARGET_LENGTH]; GetCmdArg(1, sArg, sizeof(sArg)); int target = FindTarget(client, sArg, true, true); if (target == -1) return Plugin_Handled; if (g_bBanned[target]) { ReplyToCommand(client, "[SM] %N is already couch-banned.", target); return Plugin_Handled; } DoBan(client, target); return Plugin_Handled; } public Action Command_CouchUnban(int client, int args) { if (!RequireCore(client)) return Plugin_Handled; if (args < 1) { ReplyToCommand(client, "[SM] Usage: sm_couchunban <#userid|name>"); return Plugin_Handled; } char sArg[MAX_TARGET_LENGTH]; GetCmdArg(1, sArg, sizeof(sArg)); int target = FindTarget(client, sArg, true, true); if (target == -1) return Plugin_Handled; if (!g_bBanned[target]) { ReplyToCommand(client, "[SM] %N is not couch-banned.", target); return Plugin_Handled; } DoUnban(client, target); return Plugin_Handled; } bool RequireCore(int client) { if (g_bCoreLoaded) return true; ReplyToCommand(client, "[SM] The Couch core plugin is not loaded."); return false; } // --------------------------------------------------------------------------------------------- // Actions // --------------------------------------------------------------------------------------------- void DoBan(int client, int target) { g_bBanned[target] = true; RememberAccount(target); Couch_SetClientBlocked(target, true); // Announced to everyone so players know why the couch stopped reacting to someone. PrintToChatAll("\x01[SM] \x04%N\x01 couch-banned \x04%N\x01 until the end of the map.", client, target); LogAction(client, target, "\"%L\" couch-banned \"%L\" until the end of the map.", client, target); } void DoUnban(int client, int target) { g_bBanned[target] = false; ForgetAccount(target); Couch_SetClientBlocked(target, false); PrintToChatAll("\x01[SM] \x04%N\x01 removed couch-ban from \x04%N\x01.", client, target); LogAction(client, target, "\"%L\" removed couch-ban from \"%L\".", client, target); } // An unauthenticated client has no account id, so their ban ends with the session. void RememberAccount(int client) { int account = GetSteamAccountID(client); if (account != 0 && g_alBannedAccounts.FindValue(account) == -1) g_alBannedAccounts.Push(account); } void ForgetAccount(int client) { int account = GetSteamAccountID(client); if (account == 0) return; int index = g_alBannedAccounts.FindValue(account); if (index != -1) g_alBannedAccounts.Erase(index); }