288 lines
7.4 KiB
SourcePawn
288 lines
7.4 KiB
SourcePawn
#include <sourcemod>
|
|
#include <Couch>
|
|
|
|
#pragma semicolon 1
|
|
#pragma newdecls required
|
|
|
|
// 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"); // Target errors and multi-target names ("all players").
|
|
|
|
g_alBannedAccounts = new ArrayList();
|
|
|
|
RegAdminCmd("sm_couchban", Command_CouchBan, ADMFLAG_BAN, "sm_couchban <#userid|name|@filter> - bans players 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|@filter> - lifts couch bans");
|
|
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, PREFIX_PLAIN ... " Usage: sm_couchban <#userid|name|@filter>");
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
ProcessBanCommand(client, true);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_CouchUnban(int client, int args)
|
|
{
|
|
if (!RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
if (args < 1)
|
|
{
|
|
ReplyToCommand(client, PREFIX_PLAIN ... " Usage: sm_couchunban <#userid|name|@filter>");
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
ProcessBanCommand(client, false);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Standard SourceMod targeting: a name, #userid or @filter (@all, @t, @zombies ...). Players already in
|
|
// the wanted state are skipped. One chat line covers the whole command; each ban is still logged.
|
|
void ProcessBanCommand(int client, bool bBan)
|
|
{
|
|
char sArg[MAX_TARGET_LENGTH];
|
|
GetCmdArg(1, sArg, sizeof(sArg));
|
|
|
|
int targets[MAXPLAYERS];
|
|
char sTargetName[MAX_TARGET_LENGTH];
|
|
bool bIsML;
|
|
|
|
int count = ProcessTargetString(sArg, client, targets, sizeof(targets), COMMAND_FILTER_CONNECTED | COMMAND_FILTER_NO_BOTS, sTargetName, sizeof(sTargetName), bIsML);
|
|
if (count <= 0)
|
|
{
|
|
ReplyToTargetError(client, count);
|
|
return;
|
|
}
|
|
|
|
int affected = 0;
|
|
int last = 0;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
int target = targets[i];
|
|
if (!IsClientInGame(target) || g_bBanned[target] == bBan)
|
|
continue;
|
|
|
|
if (bBan)
|
|
DoBan(client, target);
|
|
else
|
|
DoUnban(client, target);
|
|
|
|
affected++;
|
|
last = target;
|
|
}
|
|
|
|
if (affected == 0)
|
|
{
|
|
if (count == 1)
|
|
ReplyToCommand(client, PREFIX_PLAIN ... " %N is %s couch-banned.", targets[0], bBan ? "already" : "not");
|
|
else
|
|
ReplyToCommand(client, PREFIX_PLAIN ... " No matching players to couch-%s.", bBan ? "ban" : "unban");
|
|
|
|
return;
|
|
}
|
|
|
|
// Announced to everyone so players know why the couch stopped reacting to someone. A single
|
|
// player is named even when they were picked out of a filter.
|
|
if (affected == 1)
|
|
{
|
|
if (bBan)
|
|
ShowActivity2(client, PREFIX, "\x01couch-banned \x04%N\x01 until the end of the map.", last);
|
|
else
|
|
ShowActivity2(client, PREFIX, "\x01removed couch-ban from \x04%N\x01.", last);
|
|
}
|
|
else if (bIsML)
|
|
{
|
|
if (bBan)
|
|
ShowActivity2(client, PREFIX, "\x01couch-banned \x04%t\x01 until the end of the map.", sTargetName);
|
|
else
|
|
ShowActivity2(client, PREFIX, "\x01removed couch-ban from \x04%t\x01.", sTargetName);
|
|
}
|
|
else
|
|
{
|
|
if (bBan)
|
|
ShowActivity2(client, PREFIX, "\x01couch-banned \x04%s\x01 until the end of the map.", sTargetName);
|
|
else
|
|
ShowActivity2(client, PREFIX, "\x01removed couch-ban from \x04%s\x01.", sTargetName);
|
|
}
|
|
}
|
|
|
|
bool RequireCore(int client)
|
|
{
|
|
if (g_bCoreLoaded)
|
|
return true;
|
|
|
|
ReplyToCommand(client, PREFIX_PLAIN ... " The Couch core plugin is not loaded.");
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Actions
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
// The announcement is the command's job, so a filter doesn't produce a line per player.
|
|
void DoBan(int client, int target)
|
|
{
|
|
g_bBanned[target] = true;
|
|
RememberAccount(target);
|
|
Couch_SetClientBlocked(target, true);
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|