added sm_listcomms to sourceban-checker plugin

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceBans Checker 1.0.2
//   Copyright (C) 2010-2013 Nicholas Hastings
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1288490>
//
// *************************************************************************

#include <sourcemod>

#define VERSION "1.6.3-PRE"
#define LISTBANS_USAGE "sm_listbans <#userid|name> - Lists a user's prior bans from Sourcebans"
#define LISTCOMMS_USAGE "sm_listcomms <#userid|name> - Lists a user's prior comms from Sourcebans"
#define INVALID_TARGET -1

new String:g_DatabasePrefix[10] = "sb";
new Handle:g_ConfigParser;
new Handle:g_DB;

ConVar ShortMessage;

public Plugin:myinfo =
{
	name = "SourceBans++: Bans Checker",
	author = "psychonic, Ca$h Munny, SourceBans++ Dev Team",
	description = "Notifies admins of prior bans from Sourcebans upon player connect.",
	version = VERSION,
	url = "https://sbpp.github.io"
};

public OnPluginStart()
{
	LoadTranslations("common.phrases");

	CreateConVar("sbchecker_version", VERSION, "", FCVAR_NOTIFY);

	ShortMessage = CreateConVar("sb_short_message", "0", "Use shorter message for displying prev bans", _, true, 0.0, true, 1.0);

	RegAdminCmd("sm_listsbbans", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE); // for backward compatibility with the old version
	RegAdminCmd("sm_listbans", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE);
	RegAdminCmd("sm_listcomms", OnListSourceCommsCmd, ADMFLAG_BAN, LISTCOMMS_USAGE);
	RegAdminCmd("sb_reload", OnReloadCmd, ADMFLAG_RCON, "Reload sourcebans config and ban reason menu options");

	SQL_TConnect(OnDatabaseConnected, "sourcebans");
}

public OnMapStart()
{
	ReadConfig();
}

public Action:OnReloadCmd(client, args)
{
	ReadConfig();
	return Plugin_Handled;
}

public OnDatabaseConnected(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
		SetFailState("Failed to connect to SourceBans DB, %s", error);

	g_DB = hndl;
}

public OnClientAuthorized(client, const String:auth[])
{
	if (g_DB == INVALID_HANDLE)
		return;

	/* Do not check bots nor check player with lan steamid. */
	if (auth[0] == 'B' || auth[9] == 'L')
		return;

	decl String:query[512], String:ip[30];
	GetClientIP(client, ip, sizeof(ip));
	FormatEx(query, sizeof(query), "SELECT COUNT(bid) FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, auth[8], ip);

	SQL_TQuery(g_DB, OnConnectBanCheck, query, GetClientUserId(client), DBPrio_Low);
}

public OnConnectBanCheck(Handle:owner, Handle:hndl, const String:error[], any:userid)
{
	new client = GetClientOfUserId(userid);

	if (!client || hndl == INVALID_HANDLE || !SQL_FetchRow(hndl))
		return;

	new bancount = SQL_FetchInt(hndl, 0);
	if (bancount > 0)
	{
		if (ShortMessage.BoolValue)
		{
			PrintToBanAdmins("\x04[SB++]\x01Player \"%N\" has %d previous ban%s.",
				client, bancount, ((bancount > 0) ? "s":""));
		}
		else
		{
			PrintToBanAdmins("\x04[SourceBans++]\x01 Warning: Player \"%N\" has %d previous ban%s on record.",
				client, bancount, ((bancount > 0) ? "s":""));
		}
	}
}

public Action:OnListSourceBansCmd(client, args)
{
	if (args < 1)
	{
		ReplyToCommand(client, LISTBANS_USAGE);
	}

	if (g_DB == INVALID_HANDLE)
	{
		ReplyToCommand(client, "Error: Database not ready.");
		return Plugin_Handled;
	}

	decl String:targetarg[64];
	GetCmdArg(1, targetarg, sizeof(targetarg));

	new target = FindTarget(client, targetarg, true, true);
	if (target == INVALID_TARGET)
	{
		ReplyToCommand(client, "Error: Could not find a target matching '%s'.", targetarg);
		return Plugin_Handled;
	}

	decl String:auth[32];
	if (!GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth))
		 || auth[0] == 'B' || auth[9] == 'L')
	{
		ReplyToCommand(client, "Error: Could not retrieve %N's steam id.", target);
		return Plugin_Handled;
	}

	decl String:query[1024], String:ip[30];
	GetClientIP(target, ip, sizeof(ip));
	FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], ip);

	decl String:targetName[MAX_NAME_LENGTH];
	GetClientName(target, targetName, sizeof(targetName));

	new Handle:pack = CreateDataPack();
	WritePackCell(pack, (client == 0) ? 0 : GetClientUserId(client));
	WritePackString(pack, targetName);

	SQL_TQuery(g_DB, OnListBans, query, pack, DBPrio_Low);

	if (client == 0)
	{
		ReplyToCommand(client, "[SourceBans++] Note: if you are using this command through an rcon tool, you will not see results.");
	}
	else
	{
		ReplyToCommand(client, "\x04[SourceBans++]\x01 Look for %N's ban results in console.", target);
	}

	return Plugin_Handled;
}

public OnListBans(Handle:owner, Handle:hndl, const String:error[], any:pack)
{
	ResetPack(pack);
	new clientuid = ReadPackCell(pack);
	new client = GetClientOfUserId(clientuid);
	decl String:targetName[MAX_NAME_LENGTH];
	ReadPackString(pack, targetName, sizeof(targetName));
	CloseHandle(pack);

	if (clientuid > 0 && client == 0)
		return;

	if (hndl == INVALID_HANDLE)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] DB error while retrieving bans for %s:\n%s", targetName, error);
		return;
	}

	if (SQL_GetRowCount(hndl) == 0)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] No bans found for %s.", targetName);
		return;
	}

	PrintListResponse(clientuid, client, "[SourceBans++] Listing bans for %s", targetName);
	PrintListResponse(clientuid, client, "Ban Date    Banned By   Length      End Date    R  Reason");
	PrintListResponse(clientuid, client, "-------------------------------------------------------------------------------");
	while (SQL_FetchRow(hndl))
	{
		new String:createddate[11] = "<Unknown> ";
		new String:bannedby[11] = "<Unknown> ";
		new String:lenstring[11] = "N/A       ";
		new String:enddate[11] = "N/A       ";
		new String:reason[28];
		new String:RemoveType[2] = " ";

		if (!SQL_IsFieldNull(hndl, 0))
		{
			FormatTime(createddate, sizeof(createddate), "%Y-%m-%d", SQL_FetchInt(hndl, 0));
		}

		if (!SQL_IsFieldNull(hndl, 1))
		{
			new size_bannedby = sizeof(bannedby);
			SQL_FetchString(hndl, 1, bannedby, size_bannedby);
			new len = SQL_FetchSize(hndl, 1);
			if (len > size_bannedby - 1)
			{
				reason[size_bannedby - 4] = '.';
				reason[size_bannedby - 3] = '.';
				reason[size_bannedby - 2] = '.';
			}
			else
			{
				for (new i = len; i < size_bannedby - 1; i++)
				{
					bannedby[i] = ' ';
				}
			}
		}

		// NOT NULL
		new size_lenstring = sizeof(lenstring);
		new length = SQL_FetchInt(hndl, 3);
		if (length == 0)
		{
			strcopy(lenstring, size_lenstring, "Permanent ");
		}
		else
		{
			new len = IntToString(length, lenstring, size_lenstring);
			if (len < size_lenstring - 1)
			{
				// change the '\0' to a ' '. the original \0 at the end will still be there
				lenstring[len] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 2))
		{
			FormatTime(enddate, sizeof(enddate), "%Y-%m-%d", SQL_FetchInt(hndl, 2));
		}

		// NOT NULL
		new reason_size = sizeof(reason);
		SQL_FetchString(hndl, 4, reason, reason_size);
		new len = SQL_FetchSize(hndl, 4);
		if (len > reason_size - 1)
		{
			reason[reason_size - 4] = '.';
			reason[reason_size - 3] = '.';
			reason[reason_size - 2] = '.';
		}
		else
		{
			for (new i = len; i < reason_size - 1; i++)
			{
				reason[i] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 5))
		{
			SQL_FetchString(hndl, 5, RemoveType, sizeof(RemoveType));
		}

		PrintListResponse(clientuid, client, "%s  %s  %s  %s  %s  %s", createddate, bannedby, lenstring, enddate, RemoveType, reason);
	}
}

public Action:OnListSourceCommsCmd(client, args)
{
	if (args < 1)
	{
		ReplyToCommand(client, LISTCOMMS_USAGE);
	}

	if (g_DB == INVALID_HANDLE)
	{
		ReplyToCommand(client, "Error: Database not ready.");
		return Plugin_Handled;
	}

	decl String:targetarg[64];
	GetCmdArg(1, targetarg, sizeof(targetarg));

	new target = FindTarget(client, targetarg, true, true);
	if (target == INVALID_TARGET)
	{
		ReplyToCommand(client, "Error: Could not find a target matching '%s'.", targetarg);
		return Plugin_Handled;
	}

	decl String:auth[32];
	if (!GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth))
		 || auth[0] == 'B' || auth[9] == 'L')
	{
		ReplyToCommand(client, "Error: Could not retrieve %N's steam id.", target);
		return Plugin_Handled;
	}

	decl String:query[1024];
	FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType, type FROM %s_comms LEFT JOIN %s_admins ON %s_comms.aid = %s_admins.aid WHERE %s_comms.authid REGEXP '^STEAM_[0-9]:%s$' AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8]);

	decl String:targetName[MAX_NAME_LENGTH];
	GetClientName(target, targetName, sizeof(targetName));

	new Handle:pack = CreateDataPack();
	WritePackCell(pack, (client == 0) ? 0 : GetClientUserId(client));
	WritePackString(pack, targetName);

	SQL_TQuery(g_DB, OnListComms, query, pack, DBPrio_Low);

	if (client == 0)
	{
		ReplyToCommand(client, "[SourceBans++] Note: if you are using this command through an rcon tool, you will not see results.");
	}
	else
	{
		ReplyToCommand(client, "\x04[SourceBans++]\x01 Look for %N's comm results in console.", target);
	}

	return Plugin_Handled;
}

public OnListComms(Handle:owner, Handle:hndl, const String:error[], any:pack)
{
	ResetPack(pack);
	new clientuid = ReadPackCell(pack);
	new client = GetClientOfUserId(clientuid);
	decl String:targetName[MAX_NAME_LENGTH];
	ReadPackString(pack, targetName, sizeof(targetName));
	CloseHandle(pack);

	if (clientuid > 0 && client == 0)
		return;

	if (hndl == INVALID_HANDLE)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] DB error while retrieving comms for %s:\n%s", targetName, error);
		return;
	}

	if (SQL_GetRowCount(hndl) == 0)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] No comms found for %s.", targetName);
		return;
	}

	PrintListResponse(clientuid, client, "[SourceBans++] Listing comms for %s", targetName);
	PrintListResponse(clientuid, client, "Ban Date    Banned By   Length      End Date    T  R  Reason");
	PrintListResponse(clientuid, client, "-------------------------------------------------------------------------------");
	while (SQL_FetchRow(hndl))
	{
		new String:createddate[11] = "<Unknown> ";
		new String:bannedby[11] = "<Unknown> ";
		new String:lenstring[11] = "N/A       ";
		new String:enddate[11] = "N/A       ";
		new String:reason[23];
		new String:CommType[2] = " ";
		new String:RemoveType[2] = " ";

		if (!SQL_IsFieldNull(hndl, 0))
		{
			FormatTime(createddate, sizeof(createddate), "%Y-%m-%d", SQL_FetchInt(hndl, 0));
		}

		if (!SQL_IsFieldNull(hndl, 1))
		{
			new size_bannedby = sizeof(bannedby);
			SQL_FetchString(hndl, 1, bannedby, size_bannedby);
			new len = SQL_FetchSize(hndl, 1);
			if (len > size_bannedby - 1)
			{
				reason[size_bannedby - 4] = '.';
				reason[size_bannedby - 3] = '.';
				reason[size_bannedby - 2] = '.';
			}
			else
			{
				for (new i = len; i < size_bannedby - 1; i++)
				{
					bannedby[i] = ' ';
				}
			}
		}

		// NOT NULL
		new size_lenstring = sizeof(lenstring);
		new length = SQL_FetchInt(hndl, 3);
		if (length == 0)
		{
			strcopy(lenstring, size_lenstring, "Permanent ");
		}
		else
		{
			new len = IntToString(length, lenstring, size_lenstring);
			if (len < size_lenstring - 1)
			{
				// change the '\0' to a ' '. the original \0 at the end will still be there
				lenstring[len] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 2))
		{
			FormatTime(enddate, sizeof(enddate), "%Y-%m-%d", SQL_FetchInt(hndl, 2));
		}

		// NOT NULL
		new reason_size = sizeof(reason);
		SQL_FetchString(hndl, 4, reason, reason_size);
		new len = SQL_FetchSize(hndl, 4);
		if (len > reason_size - 1)
		{
			reason[reason_size - 4] = '.';
			reason[reason_size - 3] = '.';
			reason[reason_size - 2] = '.';
		}
		else
		{
			for (new i = len; i < reason_size - 1; i++)
			{
				reason[i] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 5))
		{
			SQL_FetchString(hndl, 5, RemoveType, sizeof(RemoveType));
		}
		// NOT NULL
		SQL_FetchString(hndl, 6, CommType, sizeof(RemoveType));
		if(StrEqual(CommType,"1"))
			strcopy(CommType, sizeof(CommType), "M");
		if(StrEqual(CommType,"2"))
			strcopy(CommType, sizeof(CommType), "G");

		PrintListResponse(clientuid, client, "%s  %s  %s  %s  %s  %s  %s", createddate, bannedby, lenstring, enddate, CommType, RemoveType, reason);
		//PrintListResponse(clientuid, client, "%s  %s  %s  %s  %s  %s ", createddate, bannedby, lenstring, enddate, CommType, RemoveType);

	}
}

PrintListResponse(userid, client, const String:format[], any:...)
{
	decl String:msg[192];
	VFormat(msg, sizeof(msg), format, 4);

	if (userid == 0)
	{
		PrintToServer("%s", msg);
	}
	else
	{
		PrintToConsole(client, "%s", msg);
	}
}

PrintToBanAdmins(const String:format[], any:...)
{
	decl String:msg[128];
	VFormat(msg, sizeof(msg), format, 2);

	for (new i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && !IsFakeClient(i)
			 && CheckCommandAccess(i, "sm_listsourcebans", ADMFLAG_BAN)
			)
		{
			PrintToChat(i, "%s", msg);
		}
	}
}

stock ReadConfig()
{
	InitializeConfigParser();

	if (g_ConfigParser == INVALID_HANDLE)
	{
		return;
	}

	decl String:ConfigFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, ConfigFile, sizeof(ConfigFile), "configs/sourcebans/sourcebans.cfg");

	if (FileExists(ConfigFile))
	{
		InternalReadConfig(ConfigFile);
	}
	else
	{
		decl String:Error[PLATFORM_MAX_PATH + 64];
		FormatEx(Error, sizeof(Error), "FATAL *** ERROR *** can not find %s", ConfigFile);
		SetFailState(Error);
	}
}

static InitializeConfigParser()
{
	if (g_ConfigParser == INVALID_HANDLE)
	{
		g_ConfigParser = SMC_CreateParser();
		SMC_SetReaders(g_ConfigParser, ReadConfig_NewSection, ReadConfig_KeyValue, ReadConfig_EndSection);
	}
}

static InternalReadConfig(const String:path[])
{
	new SMCError:err = SMC_ParseFile(g_ConfigParser, path);

	if (err != SMCError_Okay)
	{
		decl String:buffer[64];
		PrintToServer("%s", SMC_GetErrorString(err, buffer, sizeof(buffer)) ? buffer : "Fatal parse error");
	}
}

public SMCResult:ReadConfig_NewSection(Handle:smc, const String:name[], bool:opt_quotes)
{
	return SMCParse_Continue;
}

public SMCResult:ReadConfig_KeyValue(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
{
	if (strcmp("DatabasePrefix", key, false) == 0)
	{
		strcopy(g_DatabasePrefix, sizeof(g_DatabasePrefix), value);

		if (g_DatabasePrefix[0] == '\0')
		{
			g_DatabasePrefix = "sb";
		}
	}

	return SMCParse_Continue;
}

public SMCResult:ReadConfig_EndSection(Handle:smc)
{
	return SMCParse_Continue;
}

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 SourceBans++ Dev Team <https://github.com/sbpp>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work(s) covered by the following copyright(s):
//
//   SourceBans Checker 1.0.2
//   Copyright (C) 2010-2013 Nicholas Hastings
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1288490>
//
// *************************************************************************

#include <sourcemod>

#define VERSION "1.6.3-PRE"
#define LISTBANS_USAGE "sm_listbans <#userid|name> - Lists a user's prior bans from Sourcebans"
#define INVALID_TARGET -1

new String:g_DatabasePrefix[10] = "sb";
new Handle:g_ConfigParser;
new Handle:g_DB;

ConVar ShortMessage;

public Plugin:myinfo =
{
	name = "SourceBans++: Bans Checker",
	author = "psychonic, Ca$h Munny, SourceBans++ Dev Team",
	description = "Notifies admins of prior bans from Sourcebans upon player connect.",
	version = VERSION,
	url = "https://sbpp.github.io"
};

public OnPluginStart()
{
	LoadTranslations("common.phrases");

	CreateConVar("sbchecker_version", VERSION, "", FCVAR_NOTIFY);

	ShortMessage = CreateConVar("sb_short_message", "0", "Use shorter message for displying prev bans", _, true, 0.0, true, 1.0);

	RegAdminCmd("sm_listsbbans", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE); // for backward compatibility with the old version
	RegAdminCmd("sm_listbans", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE);
	RegAdminCmd("sb_reload", OnReloadCmd, ADMFLAG_RCON, "Reload sourcebans config and ban reason menu options");

	SQL_TConnect(OnDatabaseConnected, "sourcebans");
}

public OnMapStart()
{
	ReadConfig();
}

public Action:OnReloadCmd(client, args)
{
	ReadConfig();
	return Plugin_Handled;
}

public OnDatabaseConnected(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
		SetFailState("Failed to connect to SourceBans DB, %s", error);

	g_DB = hndl;
}

public OnClientAuthorized(client, const String:auth[])
{
	if (g_DB == INVALID_HANDLE)
		return;

	/* Do not check bots nor check player with lan steamid. */
	if (auth[0] == 'B' || auth[9] == 'L')
		return;

	decl String:query[512], String:ip[30];
	GetClientIP(client, ip, sizeof(ip));
	FormatEx(query, sizeof(query), "SELECT COUNT(bid) FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, auth[8], ip);

	SQL_TQuery(g_DB, OnConnectBanCheck, query, GetClientUserId(client), DBPrio_Low);
}

public OnConnectBanCheck(Handle:owner, Handle:hndl, const String:error[], any:userid)
{
	new client = GetClientOfUserId(userid);

	if (!client || hndl == INVALID_HANDLE || !SQL_FetchRow(hndl))
		return;

	new bancount = SQL_FetchInt(hndl, 0);
	if (bancount > 0)
	{
		if (ShortMessage.BoolValue)
		{
			PrintToBanAdmins("\x04[SB++]\x01Player \"%N\" has %d previous ban%s.",
				client, bancount, ((bancount > 0) ? "s":""));
		}
		else
		{
			PrintToBanAdmins("\x04[SourceBans++]\x01 Warning: Player \"%N\" has %d previous ban%s on record.",
				client, bancount, ((bancount > 0) ? "s":""));
		}
	}
}

public Action:OnListSourceBansCmd(client, args)
{
	if (args < 1)
	{
		ReplyToCommand(client, LISTBANS_USAGE);
	}

	if (g_DB == INVALID_HANDLE)
	{
		ReplyToCommand(client, "Error: Database not ready.");
		return Plugin_Handled;
	}

	decl String:targetarg[64];
	GetCmdArg(1, targetarg, sizeof(targetarg));

	new target = FindTarget(client, targetarg, true, true);
	if (target == INVALID_TARGET)
	{
		ReplyToCommand(client, "Error: Could not find a target matching '%s'.", targetarg);
		return Plugin_Handled;
	}

	decl String:auth[32];
	if (!GetClientAuthId(target, AuthId_Steam2, auth, sizeof(auth))
		 || auth[0] == 'B' || auth[9] == 'L')
	{
		ReplyToCommand(client, "Error: Could not retrieve %N's steam id.", target);
		return Plugin_Handled;
	}

	decl String:query[1024], String:ip[30];
	GetClientIP(target, ip, sizeof(ip));
	FormatEx(query, sizeof(query), "SELECT created, %s_admins.user, ends, length, reason, RemoveType FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL)", g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, g_DatabasePrefix, auth[8], ip);

	decl String:targetName[MAX_NAME_LENGTH];
	GetClientName(target, targetName, sizeof(targetName));

	new Handle:pack = CreateDataPack();
	WritePackCell(pack, (client == 0) ? 0 : GetClientUserId(client));
	WritePackString(pack, targetName);

	SQL_TQuery(g_DB, OnListBans, query, pack, DBPrio_Low);

	if (client == 0)
	{
		ReplyToCommand(client, "[SourceBans++] Note: if you are using this command through an rcon tool, you will not see results.");
	}
	else
	{
		ReplyToCommand(client, "\x04[SourceBans++]\x01 Look for %N's ban results in console.", target);
	}

	return Plugin_Handled;
}

public OnListBans(Handle:owner, Handle:hndl, const String:error[], any:pack)
{
	ResetPack(pack);
	new clientuid = ReadPackCell(pack);
	new client = GetClientOfUserId(clientuid);
	decl String:targetName[MAX_NAME_LENGTH];
	ReadPackString(pack, targetName, sizeof(targetName));
	CloseHandle(pack);

	if (clientuid > 0 && client == 0)
		return;

	if (hndl == INVALID_HANDLE)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] DB error while retrieving bans for %s:\n%s", targetName, error);
		return;
	}

	if (SQL_GetRowCount(hndl) == 0)
	{
		PrintListResponse(clientuid, client, "[SourceBans++] No bans found for %s.", targetName);
		return;
	}

	PrintListResponse(clientuid, client, "[SourceBans++] Listing bans for %s", targetName);
	PrintListResponse(clientuid, client, "Ban Date    Banned By   Length      End Date    R  Reason");
	PrintListResponse(clientuid, client, "-------------------------------------------------------------------------------");
	while (SQL_FetchRow(hndl))
	{
		new String:createddate[11] = "<Unknown> ";
		new String:bannedby[11] = "<Unknown> ";
		new String:lenstring[11] = "N/A       ";
		new String:enddate[11] = "N/A       ";
		decl String:reason[28];
		new String:RemoveType[2] = " ";

		if (!SQL_IsFieldNull(hndl, 0))
		{
			FormatTime(createddate, sizeof(createddate), "%Y-%m-%d", SQL_FetchInt(hndl, 0));
		}

		if (!SQL_IsFieldNull(hndl, 1))
		{
			new size_bannedby = sizeof(bannedby);
			SQL_FetchString(hndl, 1, bannedby, size_bannedby);
			new len = SQL_FetchSize(hndl, 1);
			if (len > size_bannedby - 1)
			{
				reason[size_bannedby - 4] = '.';
				reason[size_bannedby - 3] = '.';
				reason[size_bannedby - 2] = '.';
			}
			else
			{
				for (new i = len; i < size_bannedby - 1; i++)
				{
					bannedby[i] = ' ';
				}
			}
		}

		// NOT NULL
		new size_lenstring = sizeof(lenstring);
		new length = SQL_FetchInt(hndl, 3);
		if (length == 0)
		{
			strcopy(lenstring, size_lenstring, "Permanent ");
		}
		else
		{
			new len = IntToString(length, lenstring, size_lenstring);
			if (len < size_lenstring - 1)
			{
				// change the '\0' to a ' '. the original \0 at the end will still be there
				lenstring[len] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 2))
		{
			FormatTime(enddate, sizeof(enddate), "%Y-%m-%d", SQL_FetchInt(hndl, 2));
		}

		// NOT NULL
		new reason_size = sizeof(reason);
		SQL_FetchString(hndl, 4, reason, reason_size);
		new len = SQL_FetchSize(hndl, 4);
		if (len > reason_size - 1)
		{
			reason[reason_size - 4] = '.';
			reason[reason_size - 3] = '.';
			reason[reason_size - 2] = '.';
		}
		else
		{
			for (new i = len; i < reason_size - 1; i++)
			{
				reason[i] = ' ';
			}
		}

		if (!SQL_IsFieldNull(hndl, 5))
		{
			SQL_FetchString(hndl, 5, RemoveType, sizeof(RemoveType));
		}

		PrintListResponse(clientuid, client, "%s  %s  %s  %s  %s  %s", createddate, bannedby, lenstring, enddate, RemoveType, reason);
	}
}

PrintListResponse(userid, client, const String:format[], any:...)
{
	decl String:msg[192];
	VFormat(msg, sizeof(msg), format, 4);

	if (userid == 0)
	{
		PrintToServer("%s", msg);
	}
	else
	{
		PrintToConsole(client, "%s", msg);
	}
}

PrintToBanAdmins(const String:format[], any:...)
{
	decl String:msg[128];
	VFormat(msg, sizeof(msg), format, 2);

	for (new i = 1; i <= MaxClients; i++)
	{
		if (IsClientInGame(i) && !IsFakeClient(i)
			 && CheckCommandAccess(i, "sm_listsourcebans", ADMFLAG_BAN)
			)
		{
			PrintToChat(i, "%s", msg);
		}
	}
}

stock ReadConfig()
{
	InitializeConfigParser();

	if (g_ConfigParser == INVALID_HANDLE)
	{
		return;
	}

	decl String:ConfigFile[PLATFORM_MAX_PATH];
	BuildPath(Path_SM, ConfigFile, sizeof(ConfigFile), "configs/sourcebans/sourcebans.cfg");

	if (FileExists(ConfigFile))
	{
		InternalReadConfig(ConfigFile);
	}
	else
	{
		decl String:Error[PLATFORM_MAX_PATH + 64];
		FormatEx(Error, sizeof(Error), "FATAL *** ERROR *** can not find %s", ConfigFile);
		SetFailState(Error);
	}
}

static InitializeConfigParser()
{
	if (g_ConfigParser == INVALID_HANDLE)
	{
		g_ConfigParser = SMC_CreateParser();
		SMC_SetReaders(g_ConfigParser, ReadConfig_NewSection, ReadConfig_KeyValue, ReadConfig_EndSection);
	}
}

static InternalReadConfig(const String:path[])
{
	new SMCError:err = SMC_ParseFile(g_ConfigParser, path);

	if (err != SMCError_Okay)
	{
		decl String:buffer[64];
		PrintToServer("%s", SMC_GetErrorString(err, buffer, sizeof(buffer)) ? buffer : "Fatal parse error");
	}
}

public SMCResult:ReadConfig_NewSection(Handle:smc, const String:name[], bool:opt_quotes)
{
	return SMCParse_Continue;
}

public SMCResult:ReadConfig_KeyValue(Handle:smc, const String:key[], const String:value[], bool:key_quotes, bool:value_quotes)
{
	if (strcmp("DatabasePrefix", key, false) == 0)
	{
		strcopy(g_DatabasePrefix, sizeof(g_DatabasePrefix), value);

		if (g_DatabasePrefix[0] == '\0')
		{
			g_DatabasePrefix = "sb";
		}
	}

	return SMCParse_Continue;
}

public SMCResult:ReadConfig_EndSection(Handle:smc)
{
	return SMCParse_Continue;
}


Use /var/lib/apt/lists/partial for Debian based OS's

The previous deb.debian.org_debian_dists_{Debian.release-name}_Release
method didn't work either because the name of list won't necessarily
be in that pattern. If this doesn't work then we might have to leave
something in /tmp

constant Int $:pkglist-stale = 60*60*24;

constant File $:pkglist-path = on {
    Debian { "/var/lib/apt/lists/"  }
    Alpine { "/var/cache/apk/"  }
    RHEL   { "/var/cache/yum"  }
}

constant Cmd $?pm is logged-as("\c[PACKAGE]") = on {
    Debian { 'apt-get' }
    Alpine { 'apk' }
    RHEL   { 'yum' }
}

#| a Pkg represents a package installable via the system's package
#| manager, like `apt-get` or `yum`.
#|{
    if not Pkg<curl> {
        .install;
        say "installed $_ {.version}";
    }
}
class Pkg {

    #| Tells the system specific package manager to update its list of pacakges.
    static method update-pkglist? on {
        Debian ${$?pm update >debug/warn}
        RHEL   {
            ${$?pm clean expire-cache >debug/warn} &&
              ${$?pm check-update >debug/warn};
            $? != 1
        }
                # apt is noisy for no reason so send it to debug instead of warn
        Alpine ${ $?pm update *>debug }
    }

    static method last-updated-->DateTime on {
        Debian {
            # "partial" seems to get touched whenever apt-get update is run
            $:pkglist-path.add("partial").ctime;
        }
        Alpine { $:pkglist-path.find(name => '*.gz')[0].ctime }
        RHEL ${
            date -d
            ${
                $?pm repolist enabled -v !>warn
                | grep -m1 Repo-expire
                | sed -r 's/.*last: |\)//g'
             }
            '+%FT%T.%3N'
        }
    }

    static method check-update? {
        my $last-updated = Pkg.last-updated;
        if !$last-updated || now().posix - $last-updated.posix > $:pkglist-stale {
            info "Updating package list because it " ~ (
                $last-updated
                  ?? "was last updated at $last-updated"
                  !! "doesn't exist"
            );
            Pkg.update-pkglist;
        }
    }

    #| Installs the package via the builtin package manager.
    #| Returns true if the package was successfully installed.
    method install? {
        $self-->List[Pkg].install;
    }

    method ensure-install? {
        $self.installed || $self.install;
    }

    #| Returns True if a version of the package is installed.
    method installed? on {
        Debian ${dpkg -s $self *>X}
        RHEL   ${yum list installed $self *>X}
        Alpine ${apk info -e $self}
    }

    #| Gets the version of the currently installed package
    method version~ on {
        Debian ${dpkg -s $self |sed -n 's/^Version: //p' }
        RHEL   ${yum info installed $self |sed -n 's/Version *: //p'}
        Alpine ${apk version $self | sed -rn '2s/\w+-(\S*).*/\1/p' }
    }

    #| In Bool context Pkgs return `.installed`
    method Bool { $self.installed }

    method prompt-install {
        if prompt("This script requires the '$self' to be installed to continue.\ninstall?", :default) {
            $self.install;
        } else {
            die "unable to install $self";
        }
    }
}

augment List[Pkg] {

    #| Installs the package via the builtin package manager.
    #| Returns true if the package was successfully installed.
    method install? on {
        RHEL ${$?pm install -y @$self >debug/warn}
        Debian {
            Pkg.check-update;
            ${
                $?pm install >debug/warn :DEBIAN_FRONTEND<noninteractive>
                -y -q --no-install-recommends @$self
            }
        }
        Alpine {
            Pkg.check-update;
            ${$?pm add @$self --no-progress >debug/warn};
        }
    }
}

constant Int $:pkglist-stale = 60*60*24;

constant File $:pkglist-path = on {
    Debian { "/var/lib/apt/lists/"  }
    Alpine { "/var/cache/apk/"  }
    RHEL   { "/var/cache/yum"  }
}

constant Cmd $?pm is logged-as("\c[PACKAGE]") = on {
    Debian { 'apt-get' }
    Alpine { 'apk' }
    RHEL   { 'yum' }
}

#| a Pkg represents a package installable via the system's package
#| manager, like `apt-get` or `yum`.
#|{
    if not Pkg<curl> {
        .install;
        say "installed $_ {.version}";
    }
}
class Pkg {

    #| Tells the system specific package manager to update its list of pacakges.
    static method update-pkglist? on {
        Debian ${$?pm update >debug/warn}
        RHEL   {
            ${$?pm clean expire-cache >debug/warn} &&
              ${$?pm check-update >debug/warn};
            $? != 1
        }
                # apt is noisy for no reason so send it to debug instead of warn
        Alpine ${ $?pm update *>debug }
    }

    static method last-updated-->DateTime on {
        Debian {
            $:pkglist-path
              .add("deb.debian.org_debian_dists_{Debian.release-name}_Release")
              .ctime;
        }
        Alpine { $:pkglist-path.find(name => '*.gz')[0].ctime }
        RHEL ${
            date -d
            ${
                $?pm repolist enabled -v !>warn
                | grep -m1 Repo-expire
                | sed -r 's/.*last: |\)//g'
             }
            '+%FT%T.%3N'
        }
    }

    static method check-update? {
        my $last-updated = Pkg.last-updated;
        if !$last-updated || now().posix - $last-updated.posix > $:pkglist-stale {
            info "Updating package list because it " ~ (
                $last-updated
                  ?? "was last updated at $last-updated"
                  !! "doesn't exist at $:pkglist-path"
            );
            Pkg.update-pkglist;
        }
    }

    #| Installs the package via the builtin package manager.
    #| Returns true if the package was successfully installed.
    method install? {
        $self-->List[Pkg].install;
    }

    method ensure-install? {
        $self.installed || $self.install;
    }

    #| Returns True if a version of the package is installed.
    method installed? on {
        Debian ${dpkg -s $self *>X}
        RHEL   ${yum list installed $self *>X}
        Alpine ${apk info -e $self}
    }

    #| Gets the version of the currently installed package
    method version~ on {
        Debian ${dpkg -s $self |sed -n 's/^Version: //p' }
        RHEL   ${yum info installed $self |sed -n 's/Version *: //p'}
        Alpine ${apk version $self | sed -rn '2s/\w+-(\S*).*/\1/p' }
    }

    #| In Bool context Pkgs return `.installed`
    method Bool { $self.installed }

    method prompt-install {
        if prompt("This script requires the '$self' to be installed to continue.\ninstall?", :default) {
            $self.install;
        } else {
            die "unable to install $self";
        }
    }
}

augment List[Pkg] {

    #| Installs the package via the builtin package manager.
    #| Returns true if the package was successfully installed.
    method install? on {
        RHEL ${$?pm install -y @$self >debug/warn}
        Debian {
            Pkg.check-update;
            ${
                $?pm install >debug/warn :DEBIAN_FRONTEND<noninteractive>
                -y -q --no-install-recommends @$self
            }
        }
        Alpine {
            Pkg.check-update;
            ${$?pm add @$self --no-progress >debug/warn};
        }
    }
}


Fix rtv not change map

Fix rtv not change map

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <mapchooser_redux>
#include <smutils>

#undef REQUIRE_PLUGIN
#include <fys.pupd>

bool g_bAllowRTV;
bool g_bInChange;
bool g_bVoted[MAXPLAYERS+1];
int  g_iNext[MAXPLAYERS+1];
ConVar mcr_map_delay_time;

public Plugin myinfo =
{
    name        = "Rock The Vote Redux",
    author      = "Kyle",
    description = "Provides RTV Map Voting",
    version     = MCR_VERSION,
    url         = "https://www.kxnrl.com"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Pupd_CheckPlugin");
    return APLRes_Success;
}

public void OnPluginStart()
{
    SMUtils_SetChatPrefix("[\x02M\x04C\x0CR\x01]");
    SMUtils_SetChatSpaces("   ");
    SMUtils_SetChatConSnd(false);
    SMUtils_SetTextDest(HUD_PRINTCENTER);

    mcr_map_delay_time = CreateConVar("mcr_map_delay_time", "600", "How much time to allow rtv in seconds.", _, true, 1.0, true, 1800.0);

    LoadTranslations("com.kxnrl.mcr.translations");

    RegAdminCmd("sm_forcertv", Command_ForceRTV, ADMFLAG_CHANGEMAP, "Force an RTV vote");

    AutoExecConfig(true, "rockthevote_redux", "sourcemod/mapchooser");
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public void OnConfigsExecuted()
{
    g_bInChange = false;
    g_bAllowRTV = false;
    CreateTimer(mcr_map_delay_time.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE);
}

public Action Timer_DelayRTV(Handle timer)
{
    g_bAllowRTV = true;
}

public void OnClientConnected(int client)
{
    g_bVoted[client] = false;
}

public void OnClientDisconnect(int client)
{
    g_bVoted[client] = false;
}

public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs)
{
    if (!client)
        return;

    if (strcmp(sArgs, "rtv", false) == 0 || strcmp(sArgs[1], "rtv", false) == 0)
        AttemptRTV(client);
}

void AttemptRTV(int client)
{
    if (!g_bAllowRTV)
    {
        Chat(client, "%T", "rtv not allowed", client);
        return;
    }

    int time = GetTime();
    if (g_iNext[client] > time)
        return;

    g_iNext[client] = time + 10;

    if (!CanMapChooserStartVote())
    {
        Chat(client, "%T", "rtv started", client);
        return;
    }

    if (HasEndOfMapVoteFinished())
    {
        char map[128];
        if (GetNextMap(map, 128))
        {
            Chat(client, "%T", "nominate vote complete", client, map);
            if (FindConVar("mcr_include_desctag").BoolValue)
            {
                char desc[128];
                if (GetMapDescEx(map, desc, 128, false, false, false, FindConVar("mcr_include_tiertag").BoolValue))
                {
                    SMUtils_SkipNextPrefix();
                    Chat(client, "\x0E ➤ \x0E ➢ \x0E ➣ \x01  \x0A[\x05%s\x0A]", desc);
                }
            }
        }
    }

    if (g_bVoted[client])
    {
        RTV_CheckStatus(client, true, true);
        return;
    }

    g_bVoted[client] = true;

    if (RTV_CheckStatus(client, true, false))
        StartRTV();
}

void StartRTV()
{
    if (g_bInChange)
        return;

    ResetRTV();
    g_bAllowRTV = false;

    if (HasEndOfMapVoteFinished())
    {
        char map[128];
        if (GetNextMap(map, 128))
        {
            g_bInChange = true;

            tChatAll("%t", "rtv change map", map);
            CreateTimer(10.0, Timer_ChangeMap, _, TIMER_FLAG_NO_MAPCHANGE);
        }

        return;
    }

    if (CanMapChooserStartVote())
    {
        InitiateMapChooserVote(MapChange_RoundEnd, null);
        CreateTimer(300.0, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE);
    }
}

void ResetRTV()
{
    for(int i = 1; i <= MaxClients+1; i++)
        g_bVoted[i] = false;
}

public Action Timer_ChangeMap(Handle timer)
{
    FindConVar("mp_halftime").SetInt(0);
    FindConVar("mp_timelimit").SetInt(1);
    FindConVar("mp_maxrounds").SetInt(0);
    FindConVar("mp_roundtime").SetInt(1);

    for(int client = 1; client <= MaxClients; ++client)
    if (IsClientInGame(client))
    if (IsPlayerAlive(client))
    ForcePlayerSuicide(client);

    return Plugin_Stop;
}

public Action Command_ForceRTV(int client, int args)
{
    StartRTV();
    tChatAll("%t", "force rtv");
    return Plugin_Handled;
}

bool RTV_CheckStatus(int client, bool notice, bool self)
{
    int need, done;
    _CheckPlayer(need, done);

    if (notice)
    {
        if (self)
            Chat(client, "%T", "rtv self", client, done, need);
        else
            tChatAll("%t", "rtv broadcast", client, done, need);
    }

    return (done >= need);
}

void _CheckPlayer(int &need, int &done)
{
    need = 0;
    done = 0;

    int players = 0;

    for(int client = 1; client <= MaxClients; client++)
        if (IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client))
        {
            players++;
            if (g_bVoted[client])
                done++;
        }

    need = RoundToCeil(players*0.6);

    if (need == 1 && players >= 2)
        need = 2;
}

#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <mapchooser_redux>
#include <smutils>

#undef REQUIRE_PLUGIN
#include <fys.pupd>

bool g_bAllowRTV;
bool g_bInChange;
bool g_bVoted[MAXPLAYERS+1];
int  g_iNext[MAXPLAYERS+1];
ConVar mcr_map_delay_time;

public Plugin myinfo =
{
    name        = "Rock The Vote Redux",
    author      = "Kyle",
    description = "Provides RTV Map Voting",
    version     = MCR_VERSION,
    url         = "https://www.kxnrl.com"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    MarkNativeAsOptional("Pupd_CheckPlugin");
    return APLRes_Success;
}

public void OnPluginStart()
{
    SMUtils_SetChatPrefix("[\x02M\x04C\x0CR\x01]");
    SMUtils_SetChatSpaces("   ");
    SMUtils_SetChatConSnd(false);
    SMUtils_SetTextDest(HUD_PRINTCENTER);

    mcr_map_delay_time = CreateConVar("mcr_map_delay_time", "600", "How much time to allow rtv in seconds.", _, true, 1.0, true, 1800.0);

    LoadTranslations("com.kxnrl.mcr.translations");

    RegAdminCmd("sm_forcertv", Command_ForceRTV, ADMFLAG_CHANGEMAP, "Force an RTV vote");

    AutoExecConfig(true, "rockthevote_redux", "sourcemod/mapchooser");
}

public void Pupd_OnCheckAllPlugins()
{
    Pupd_CheckPlugin(false, "https://build.kxnrl.com/updater/MCR/");
}

public void OnConfigsExecuted()
{
    g_bInChange = false;
    g_bAllowRTV = false;
    CreateTimer(mcr_map_delay_time.FloatValue, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE);
}

public Action Timer_DelayRTV(Handle timer)
{
    g_bAllowRTV = true;
}

public void OnClientConnected(int client)
{
    g_bVoted[client] = false;
}

public void OnClientDisconnect(int client)
{
    g_bVoted[client] = false;
}

public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs)
{
    if (!client)
        return;

    if (strcmp(sArgs, "rtv", false) == 0 || strcmp(sArgs[1], "rtv", false) == 0)
        AttemptRTV(client);
}

void AttemptRTV(int client)
{
    if (!g_bAllowRTV)
    {
        Chat(client, "%T", "rtv not allowed", client);
        return;
    }

    int time = GetTime();
    if (g_iNext[client] > time)
        return;

    g_iNext[client] = time + 10;

    if (!CanMapChooserStartVote())
    {
        Chat(client, "%T", "rtv started", client);
        return;
    }

    if (HasEndOfMapVoteFinished())
    {
        char map[128];
        if (GetNextMap(map, 128))
        {
            Chat(client, "%T", "nominate vote complete", client, map);
            if (FindConVar("mcr_include_desctag").BoolValue)
            {
                char desc[128];
                if (GetMapDescEx(map, desc, 128, false, false, false, FindConVar("mcr_include_tiertag").BoolValue))
                {
                    SMUtils_SkipNextPrefix();
                    Chat(client, "\x0E ➤ \x0E ➢ \x0E ➣ \x01  \x0A[\x05%s\x0A]", desc);
                }
            }
        }
    }

    if (g_bVoted[client])
    {
        RTV_CheckStatus(client, true, true);
        return;
    }

    g_bVoted[client] = true;

    if (RTV_CheckStatus(client, true, false))
        StartRTV();
}

void StartRTV()
{
    if (g_bInChange)
        return;

    ResetRTV();
    g_bAllowRTV = false;

    if (HasEndOfMapVoteFinished())
    {
        char map[128];
        if (GetNextMap(map, 128))
        {
            g_bInChange = true;

            tChatAll("%t", "rtv change map", map);
            CreateTimer(10.0, Timer_ChangeMap, _, TIMER_FLAG_NO_MAPCHANGE);
        }

        return;
    }

    if (CanMapChooserStartVote())
    {
        InitiateMapChooserVote(MapChange_RoundEnd, null);
        CreateTimer(300.0, Timer_DelayRTV, _, TIMER_FLAG_NO_MAPCHANGE);
    }
}

void ResetRTV()
{
    for(int i = 1; i <= MaxClients+1; i++)
        g_bVoted[i] = false;
}

public Action Timer_ChangeMap(Handle timer)
{
    FindConVar("mp_halftime").SetInt(0);
    FindConVar("mp_timelimit").SetInt(0);
    FindConVar("mp_maxrounds").SetInt(0);
    FindConVar("mp_roundtime").SetInt(1);

    for(int client = 1; client <= MaxClients; ++client)
    if (IsClientInGame(client))
    if (IsPlayerAlive(client))
    ForcePlayerSuicide(client);

    return Plugin_Stop;
}

public Action Command_ForceRTV(int client, int args)
{
    StartRTV();
    tChatAll("%t", "force rtv");
    return Plugin_Handled;
}

bool RTV_CheckStatus(int client, bool notice, bool self)
{
    int need, done;
    _CheckPlayer(need, done);

    if (notice)
    {
        if (self)
            Chat(client, "%T", "rtv self", client, done, need);
        else
            tChatAll("%t", "rtv broadcast", client, done, need);
    }

    return (done >= need);
}

void _CheckPlayer(int &need, int &done)
{
    need = 0;
    done = 0;

    int players = 0;

    for(int client = 1; client <= MaxClients; client++)
        if (IsClientInGame(client) && !IsFakeClient(client) && !IsClientSourceTV(client))
        {
            players++;
            if (g_bVoted[client])
                done++;
        }

    need = RoundToCeil(players*0.6);

    if (need == 1 && players >= 2)
        need = 2;
}


fixed warnings in SourcePawn Compiler 1.8.0.6017 (#1)

/* ------------------------------------------------------------------

                            ABOUT / CREDITS

This is a revived version of PropHunt for CS:GO, written by
Statistician (I sometimes go by the alias Tilastokeskus).

Although this plugin has essentially been written from scratch,
many snippets originate from the earlier CS:S plugin written
(and abandoned) by SelaX. Anyone who thinks I should give credit
to other people should call me out on it, and I'll make sure
they will be mentioned appropriately.

                               LICENCE

I'm publishing this under the MIT licence, but I don't really give a shit.

------------------------------------------------------------------ */

#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <sdkhooks>
#include "prophunt/include/globals.inc"
#include "prophunt/include/keyvalues.inc"
#include "prophunt/include/utils.inc"
#pragma newdecls required

#define PLUGIN_VERSION "1.0.5"

public Plugin myinfo = {
    name = "PropHunt",
    author = "Statistician",
    description = "Terrorists choose a model and hide, CTs try to find and kill them.",
    version = PLUGIN_VERSION,
    url = "http://github.com/tilastokeskus/sm-PropHunt"
};

public void OnPluginStart() {
    Handle hVersion = CreateConVar("ph_version", PLUGIN_VERSION, "PropHunt",
            FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);
    SetConVarString(hVersion, PLUGIN_VERSION);

    CreateConVars();
    RegisterCommands();
    AddListeners();
    SetOffsets();
    LoadLang();

    // init clients
    for (int x = 1; x <= MaxClients; x++) {
        if (IsClientInGame(x))
            OnClientPutInServer(x);
    }

    CreateTimer(120.0, SpamCommands, 0);
    g_hForceCamera = FindConVar("mp_forcecamera");

    AutoExecConfig(true, "prophunt");
}

public void OnPluginEnd() {
    ServerCommand("mp_restartgame 1");

    for (int client = 1; client <= MaxClients; client++) {
        if (g_hAutoFreezeTimers[client] != INVALID_HANDLE) {
            KillTimer(g_hAutoFreezeTimers[client]);
            g_hAutoFreezeTimers[client] = INVALID_HANDLE;
        }
    }
}

public void OnConfigsExecuted() {

    // set bad server cvars
    for (int i = 0; i < sizeof(protected_cvars); i++) {
        g_hProtectedConvar[i] = FindConVar(protected_cvars[i]);
        if (g_hProtectedConvar[i] == INVALID_HANDLE)
            continue;

        previous_values[i] = GetConVarInt(g_hProtectedConvar[i]);
        SetConVarInt(g_hProtectedConvar[i], forced_values[i], true);
        HookConVarChange(g_hProtectedConvar[i], OnCvarChange);
    }
}

// teach the players the /whistle and /tp commands
public Action SpamCommands(Handle timer, int data) {
    if (GetConVarBool(cvar_Whistle) && data == 1)
        PrintToChatAll("%s%t", PREFIX, "T type /whistle");
    else if (!GetConVarBool(cvar_Whistle) || data == 0) {
        for (int i = 1; i <= MaxClients; i++)
            if (IsClientInGame(i) && GetClientTeam(i) == CS_TEAM_T)
                PrintToChat(i, "%s%t", PREFIX, "T type /tp");
    }

    CreateTimer(120.0, SpamCommands, (data == 0 ? 1 : 0));
    return Plugin_Continue;
}

// prevent changes to protected cvars
public void OnCvarChange(Handle convar, const char[] oldValue, const char[] newValue) {
    char cvarName[50];
    GetConVarName(convar, cvarName, sizeof(cvarName));
    for (int i = 0; i < sizeof(protected_cvars); i++) {
        if (StrEqual(protected_cvars[i], cvarName) && StringToInt(newValue) != forced_values[i]) {
            SetConVarInt(convar, forced_values[i]);
            PrintToServer("Hide and Seek: %T", "protected cvar", LANG_SERVER);
            break;
        }
    }
}

// prevent changes to hider speed
public void OnChangeHiderSpeed(Handle convar, const char[] oldValue, const char[] newValue) {
    for (int i = 1; i <= MaxClients; i++) {
        if (IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == CS_TEAM_T)
            SetEntDataFloat(i, g_flLaggedMovementValue, GetConVarFloat(cvar_HiderSpeed), true);
    }
}

static void CreateConVars() {
    cvar_FreezeCTs = CreateConVar("ph_freezects", "1", "Should CTs get freezed and blinded on spawn?", _, true, 0.0, true, 1.0);
    cvar_FreezeTime = CreateConVar("ph_freezetime", "45.0", "How long should the CTs be freezed after spawn?", _, true, 1.00, true, 120.00);
    cvar_ChangeLimit = CreateConVar("ph_changelimit", "2", "How often a T is allowed to choose his model ingame? 0 = unlimited", _, true, 0.00);
    cvar_ChangeLimittime = CreateConVar("ph_changelimittime", "30.0", "How long should a T be allowed to change his model again after spawn?", _, true, 0.00);
    cvar_AutoChoose = CreateConVar("ph_autochoose", "0", "Should the plugin choose models for the hiders automatically?", _, true, 0.0, true, 1.0);
    cvar_Whistle = CreateConVar("ph_whistle", "1", "Are terrorists allowed to whistle?", _);
    cvar_WhistleTimes = CreateConVar("ph_whistle_times", "5", "How many times a hider is allowed to whistle per round?", _);
    cvar_WhistleSeeker = CreateConVar("ph_whistle_seeker", "0", "Allow CTs to enforce T whistle?", _);
    cvar_HiderWinFrags = CreateConVar("ph_hider_win_frags", "5", "How many frags should surviving terrorists gain?", _, true, 0.00, true, 10.00);
    cvar_SlaySeekers = CreateConVar("ph_slay_seekers", "0", "Should we slay all seekers on round end and there are still some hiders alive? (Default: 0)", _, true, 0.0, true, 1.0);
    cvar_HPSeekerEnable = CreateConVar("ph_hp_seeker_enable", "1", "Should CT lose HP when shooting, 0 = off/1 = on.", _, true, 0.0, true, 1.0);
    cvar_HPSeekerDec = CreateConVar("ph_hp_seeker_dec", "5", "How many hp should a CT lose on shooting?", _, true, 0.00);
    cvar_HPSeekerInc = CreateConVar("ph_hp_seeker_inc", "15", "How many hp should a CT gain when hitting a hider?", _, true, 0.00);
    cvar_HPSeekerIncShotgun = CreateConVar("ph_hp_seeker_inc_shotgun", "5", "How many hp should a CT gain when hitting a hider with shotgun? (CS:GO only)", _, true, 0.00);
    cvar_HPSeekerBonus = CreateConVar("ph_hp_seeker_bonus", "50", "How many hp should a CT gain when killing a hider?", _, true, 0.00);
    cvar_HiderSpeed = CreateConVar("ph_hidersspeed", "1.00", "Hiders speed (Default: 1.00).", _, true, 1.00, true, 3.00);
    cvar_DisableDucking = CreateConVar("ph_disable_ducking", "1", "Disable ducking. (Default: 1).", _, true, 0.00, true, 1.00);
    cvar_AutoThirdPerson = CreateConVar("ph_auto_thirdperson", "1", "Enable thirdperson view for hiders automatically. (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_HiderFreezeMode = CreateConVar("ph_hider_freeze_mode", "1", "0: Disables /freeze command for hiders, 1: Only freeze on position, be able to move camera, 2: Freeze completely (no cameramovements) (Default: 2)", _, true, 0.00, true, 2.00);
    cvar_HideBlood = CreateConVar("ph_hide_blood", "1", "Hide blood on hider damage. (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_ShowHelp = CreateConVar("ph_show_help", "0", "Show help explaining the game on first player spawn. (Default: 0)", _, true, 0.00, true, 1.00);
    cvar_CTRatio = CreateConVar("ph_ct_ratio", "3", "The ratio of hiders to 1 seeker. 0 to disables teambalance. (Default: 3)", _, true, 0.00, true, 64.00);
    cvar_DisableUse = CreateConVar("ph_disable_use", "1", "Disable CTs pushing things. (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_HiderFreezeInAir = CreateConVar("ph_hider_freeze_inair", "0", "Are hiders allowed to freeze in the air? (Default: 0)", _, true, 0.00, true, 1.00);
    cvar_HidePlayerLocation = CreateConVar("ph_hide_player_locations", "1", "Hide the location info shown next to players name on voice chat and teamsay? (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_AutoFreezeTime = CreateConVar("ph_auto_freeze_time", "5", "Time after which stationary players should freeze automatically (Default: 5) - 0 to disable", _, true, 0.00);
    cvar_GuaranteedCTTurns = CreateConVar("ph_guaranteed_ct_turns", "3", "Turns after which CTs might be switched to the T side (Default: 3) - 0 to never switch", _, true, 0.00);
    cvar_KnifeSpeed = CreateConVar("ph_knifespeed", "1.40", "Running speed when holding a knife (multiplier)", _, true, 0.00);
    cvar_LimitSpec = CreateConVar("ph_limitspec", "2", "0: Do not limit spectating, 1: Limit spectating to own team, 2: Limit spectating to CT. (Default: 2)", _, true, 0.00, true, 2.00);
    cvar_IncludeDefaultModels = CreateConVar("ph_include_default_models", "1", "0: Include default model list only when there isn't one for the current map, 1: Always include default model list. (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_ForcePeriodicWhistle = CreateConVar("ph_force_periodic_whistle", "0", "Periodically, every x seconds, force a random hider to whistle. 0 to disable. (Default: 0)", _, true, 0.00);
    cvar_PeriodicWhistleDelay = CreateConVar("ph_periodic_whistle_delay", "60", "Number of seconds for the first periodic whistle, if they are enabled. (Default: 60)", _, true, 0.00);
    cvar_TurnsToScramble = CreateConVar("ph_turns_to_scramble", "0", "Scramble teams every x turns. 0 to disable scrambling. Disables the /ct command if enabled. (Default: 0)", _, true, 0.00);
    cvar_CategorizeModels = CreateConVar("ph_categorize_models", "1", "Enable splitting the model menu into categories. (Default: 1)", _, true, 0.00, true, 1.00);
    cvar_DefaultCategory = CreateConVar("ph_default_category", "Map Models", "If categories are enabled, uncategorized models are put under this category. (Default: Map Models)", _);
}

static void RegisterCommands() {
    RegConsoleCmd("rules", Cmd_DisplayHelp, "Displays a panel with information on how to play.");
    RegConsoleCmd("hide", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("prop", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("model", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("tp", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("thirdperson", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("third", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("jointeam", Cmd_JoinTeam);
    RegConsoleCmd("whistle", Cmd_PlayWhistle, "Plays a random sound from the hiders position to give the seekers a hint.");
    RegConsoleCmd("whoami", Cmd_DisplayModelName, "Displays the current models description in chat.");
    RegConsoleCmd("freeze", Cmd_Freeze, "Toggles freezing for hiders.");
    RegConsoleCmd("ct", Cmd_RequestCT, "Requests a switch to the seeking side.");

    RegAdminCmd("ph_force_whistle", ForceWhistle, ADMFLAG_CHAT, "Force a player to whistle");
    RegAdminCmd("ph_reload_models", ReloadModels, ADMFLAG_RCON, "Reload the modellist from the map config file.");
    RegAdminCmd("ph_debugmodels", Debug_ModelInfo, ADMFLAG_RCON, "Print model debug info to console.");
}

static void AddListeners() {
    HookConVarChange(cvar_HiderSpeed, OnChangeHiderSpeed);

    HookEvent("player_spawn", Event_OnPlayerSpawn);
    HookEvent("weapon_fire", Event_OnWeaponFire);
    HookEvent("player_death", Event_OnPlayerDeath);
    //HookEvent("player_death", Event_OnPlayerDeath_Pre, EventHookMode_Pre);
    HookEvent("round_start", Event_OnRoundStart);
    HookEvent("round_end", Event_OnRoundEnd);
    HookEvent("round_end", Event_OnRoundEnd_Pre, EventHookMode_Pre);
    HookEvent("player_team", Event_OnPlayerTeam);
    HookEvent("player_team", Event_OnPlayerTeam_Pre, EventHookMode_Pre);
    //HookEvent("teamchange_pending", Event_OnTeamChange);
    HookEvent("item_equip", Event_ItemEquip);

    AddCommandListener(Cmd_spec_next, "spec_next");
    AddCommandListener(Cmd_spec_prev, "spec_prev");
    AddCommandListener(Cmd_spec_player, "spec_player");
    AddCommandListener(Cmd_spec_mode, "spec_mode");
}

static void SetOffsets() {
    g_Freeze = FindSendPropInfo("CBasePlayer", "m_fFlags");
    g_flLaggedMovementValue = FindSendPropInfo("CCSPlayer", "m_flLaggedMovementValue");
}

static void LoadLang() {
    LoadTranslations("plugin.prophunt");
    LoadTranslations("common.phrases");
}

#include "prophunt/roundevents.sp"
#include "prophunt/mapevents.sp"
#include "prophunt/clientevents.sp"
#include "prophunt/commands.sp"
#include "prophunt/menus.sp"
#include "prophunt/models.sp"
#include "prophunt/spectate.sp"
#include "prophunt/teamevents.sp"

/* ------------------------------------------------------------------

                            ABOUT / CREDITS

This is a revived version of PropHunt for CS:GO, written by
Statistician (I sometimes go by the alias Tilastokeskus).

Although this plugin has essentially been written from scratch,
many snippets originate from the earlier CS:S plugin written
(and abandoned) by SelaX. Anyone who thinks I should give credit
to other people should call me out on it, and I'll make sure
they will be mentioned appropriately.

                               LICENCE

I'm publishing this under the MIT licence, but I don't really give a shit.

------------------------------------------------------------------ */

#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <sdkhooks>
#include "prophunt/include/globals.inc"
#include "prophunt/include/keyvalues.inc"
#include "prophunt/include/utils.inc"
#pragma newdecls required

#define PLUGIN_VERSION "1.0.5"

public Plugin myinfo = {
    name = "PropHunt",
    author = "Statistician",
    description = "Terrorists choose a model and hide, CTs try to find and kill them.",
    version = PLUGIN_VERSION,
    url = "http://github.com/tilastokeskus/sm-PropHunt"
};

public void OnPluginStart() {
    Handle hVersion = CreateConVar("ph_version", PLUGIN_VERSION, "PropHunt",
            FCVAR_PLUGIN | FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);
    SetConVarString(hVersion, PLUGIN_VERSION);

    CreateConVars();
    RegisterCommands();
    AddListeners();
    SetOffsets();
    LoadLang();

    // init clients
    for (int x = 1; x <= MaxClients; x++) {
        if (IsClientInGame(x))
            OnClientPutInServer(x);
    }

    CreateTimer(120.0, SpamCommands, 0);
    g_hForceCamera = FindConVar("mp_forcecamera");

    AutoExecConfig(true, "prophunt");
}

public void OnPluginEnd() {
    ServerCommand("mp_restartgame 1");

    for (int client = 1; client <= MaxClients; client++) {
        if (g_hAutoFreezeTimers[client] != INVALID_HANDLE) {
            KillTimer(g_hAutoFreezeTimers[client]);
            g_hAutoFreezeTimers[client] = INVALID_HANDLE;
        }
    }
}

public void OnConfigsExecuted() {

    // set bad server cvars
    for (int i = 0; i < sizeof(protected_cvars); i++) {
        g_hProtectedConvar[i] = FindConVar(protected_cvars[i]);
        if (g_hProtectedConvar[i] == INVALID_HANDLE)
            continue;

        previous_values[i] = GetConVarInt(g_hProtectedConvar[i]);
        SetConVarInt(g_hProtectedConvar[i], forced_values[i], true);
        HookConVarChange(g_hProtectedConvar[i], OnCvarChange);
    }
}

// teach the players the /whistle and /tp commands
public Action SpamCommands(Handle timer, int data) {
    if (GetConVarBool(cvar_Whistle) && data == 1)
        PrintToChatAll("%s%t", PREFIX, "T type /whistle");
    else if (!GetConVarBool(cvar_Whistle) || data == 0) {
        for (int i = 1; i <= MaxClients; i++)
            if (IsClientInGame(i) && GetClientTeam(i) == CS_TEAM_T)
                PrintToChat(i, "%s%t", PREFIX, "T type /tp");
    }

    CreateTimer(120.0, SpamCommands, (data == 0 ? 1 : 0));
    return Plugin_Continue;
}

// prevent changes to protected cvars
public void OnCvarChange(Handle convar, const char[] oldValue, const char[] newValue) {
    char cvarName[50];
    GetConVarName(convar, cvarName, sizeof(cvarName));
    for (int i = 0; i < sizeof(protected_cvars); i++) {
        if (StrEqual(protected_cvars[i], cvarName) && StringToInt(newValue) != forced_values[i]) {
            SetConVarInt(convar, forced_values[i]);
            PrintToServer("Hide and Seek: %T", "protected cvar", LANG_SERVER);
            break;
        }
    }
}

// prevent changes to hider speed
public void OnChangeHiderSpeed(Handle convar, const char[] oldValue, const char[] newValue) {
    for (int i = 1; i <= MaxClients; i++) {
        if (IsClientInGame(i) && IsPlayerAlive(i) && GetClientTeam(i) == CS_TEAM_T)
            SetEntDataFloat(i, g_flLaggedMovementValue, GetConVarFloat(cvar_HiderSpeed), true);
    }
}

static void CreateConVars() {
    cvar_FreezeCTs = CreateConVar("ph_freezects", "1", "Should CTs get freezed and blinded on spawn?", FCVAR_PLUGIN, true, 0.0, true, 1.0);
    cvar_FreezeTime = CreateConVar("ph_freezetime", "45.0", "How long should the CTs be freezed after spawn?", FCVAR_PLUGIN, true, 1.00, true, 120.00);
    cvar_ChangeLimit = CreateConVar("ph_changelimit", "2", "How often a T is allowed to choose his model ingame? 0 = unlimited", FCVAR_PLUGIN, true, 0.00);
    cvar_ChangeLimittime = CreateConVar("ph_changelimittime", "30.0", "How long should a T be allowed to change his model again after spawn?", FCVAR_PLUGIN, true, 0.00);
    cvar_AutoChoose = CreateConVar("ph_autochoose", "0", "Should the plugin choose models for the hiders automatically?", FCVAR_PLUGIN, true, 0.0, true, 1.0);
    cvar_Whistle = CreateConVar("ph_whistle", "1", "Are terrorists allowed to whistle?", FCVAR_PLUGIN);
    cvar_WhistleTimes = CreateConVar("ph_whistle_times", "5", "How many times a hider is allowed to whistle per round?", FCVAR_PLUGIN);
    cvar_WhistleSeeker = CreateConVar("ph_whistle_seeker", "0", "Allow CTs to enforce T whistle?", FCVAR_PLUGIN);
    cvar_HiderWinFrags = CreateConVar("ph_hider_win_frags", "5", "How many frags should surviving terrorists gain?", FCVAR_PLUGIN, true, 0.00, true, 10.00);
    cvar_SlaySeekers = CreateConVar("ph_slay_seekers", "0", "Should we slay all seekers on round end and there are still some hiders alive? (Default: 0)", FCVAR_PLUGIN, true, 0.0, true, 1.0);
    cvar_HPSeekerEnable = CreateConVar("ph_hp_seeker_enable", "1", "Should CT lose HP when shooting, 0 = off/1 = on.", FCVAR_PLUGIN, true, 0.0, true, 1.0);
    cvar_HPSeekerDec = CreateConVar("ph_hp_seeker_dec", "5", "How many hp should a CT lose on shooting?", FCVAR_PLUGIN, true, 0.00);
    cvar_HPSeekerInc = CreateConVar("ph_hp_seeker_inc", "15", "How many hp should a CT gain when hitting a hider?", FCVAR_PLUGIN, true, 0.00);
    cvar_HPSeekerIncShotgun = CreateConVar("ph_hp_seeker_inc_shotgun", "5", "How many hp should a CT gain when hitting a hider with shotgun? (CS:GO only)", FCVAR_PLUGIN, true, 0.00);
    cvar_HPSeekerBonus = CreateConVar("ph_hp_seeker_bonus", "50", "How many hp should a CT gain when killing a hider?", FCVAR_PLUGIN, true, 0.00);
    cvar_HiderSpeed = CreateConVar("ph_hidersspeed", "1.00", "Hiders speed (Default: 1.00).", FCVAR_PLUGIN, true, 1.00, true, 3.00);
    cvar_DisableDucking = CreateConVar("ph_disable_ducking", "1", "Disable ducking. (Default: 1).", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_AutoThirdPerson = CreateConVar("ph_auto_thirdperson", "1", "Enable thirdperson view for hiders automatically. (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_HiderFreezeMode = CreateConVar("ph_hider_freeze_mode", "1", "0: Disables /freeze command for hiders, 1: Only freeze on position, be able to move camera, 2: Freeze completely (no cameramovements) (Default: 2)", FCVAR_PLUGIN, true, 0.00, true, 2.00);
    cvar_HideBlood = CreateConVar("ph_hide_blood", "1", "Hide blood on hider damage. (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_ShowHelp = CreateConVar("ph_show_help", "0", "Show help explaining the game on first player spawn. (Default: 0)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_CTRatio = CreateConVar("ph_ct_ratio", "3", "The ratio of hiders to 1 seeker. 0 to disables teambalance. (Default: 3)", FCVAR_PLUGIN, true, 0.00, true, 64.00);
    cvar_DisableUse = CreateConVar("ph_disable_use", "1", "Disable CTs pushing things. (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_HiderFreezeInAir = CreateConVar("ph_hider_freeze_inair", "0", "Are hiders allowed to freeze in the air? (Default: 0)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_HidePlayerLocation = CreateConVar("ph_hide_player_locations", "1", "Hide the location info shown next to players name on voice chat and teamsay? (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_AutoFreezeTime = CreateConVar("ph_auto_freeze_time", "5", "Time after which stationary players should freeze automatically (Default: 5) - 0 to disable", FCVAR_PLUGIN, true, 0.00);
    cvar_GuaranteedCTTurns = CreateConVar("ph_guaranteed_ct_turns", "3", "Turns after which CTs might be switched to the T side (Default: 3) - 0 to never switch", FCVAR_PLUGIN, true, 0.00);
    cvar_KnifeSpeed = CreateConVar("ph_knifespeed", "1.40", "Running speed when holding a knife (multiplier)", FCVAR_PLUGIN, true, 0.00);
    cvar_LimitSpec = CreateConVar("ph_limitspec", "2", "0: Do not limit spectating, 1: Limit spectating to own team, 2: Limit spectating to CT. (Default: 2)", FCVAR_PLUGIN, true, 0.00, true, 2.00);
    cvar_IncludeDefaultModels = CreateConVar("ph_include_default_models", "1", "0: Include default model list only when there isn't one for the current map, 1: Always include default model list. (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_ForcePeriodicWhistle = CreateConVar("ph_force_periodic_whistle", "0", "Periodically, every x seconds, force a random hider to whistle. 0 to disable. (Default: 0)", FCVAR_PLUGIN, true, 0.00);
    cvar_PeriodicWhistleDelay = CreateConVar("ph_periodic_whistle_delay", "60", "Number of seconds for the first periodic whistle, if they are enabled. (Default: 60)", FCVAR_PLUGIN, true, 0.00);
    cvar_TurnsToScramble = CreateConVar("ph_turns_to_scramble", "0", "Scramble teams every x turns. 0 to disable scrambling. Disables the /ct command if enabled. (Default: 0)", FCVAR_PLUGIN, true, 0.00);
    cvar_CategorizeModels = CreateConVar("ph_categorize_models", "1", "Enable splitting the model menu into categories. (Default: 1)", FCVAR_PLUGIN, true, 0.00, true, 1.00);
    cvar_DefaultCategory = CreateConVar("ph_default_category", "Map Models", "If categories are enabled, uncategorized models are put under this category. (Default: Map Models)", FCVAR_PLUGIN);
}

static void RegisterCommands() {
    RegConsoleCmd("rules", Cmd_DisplayHelp, "Displays a panel with information on how to play.");
    RegConsoleCmd("hide", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("prop", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("model", Cmd_SelectModelMenu, "Opens a menu with different models to choose as hider.");
    RegConsoleCmd("tp", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("thirdperson", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("third", Cmd_ToggleThirdPerson, "Toggles the view to thirdperson for hiders.");
    RegConsoleCmd("jointeam", Cmd_JoinTeam);
    RegConsoleCmd("whistle", Cmd_PlayWhistle, "Plays a random sound from the hiders position to give the seekers a hint.");
    RegConsoleCmd("whoami", Cmd_DisplayModelName, "Displays the current models description in chat.");
    RegConsoleCmd("freeze", Cmd_Freeze, "Toggles freezing for hiders.");
    RegConsoleCmd("ct", Cmd_RequestCT, "Requests a switch to the seeking side.");

    RegAdminCmd("ph_force_whistle", ForceWhistle, ADMFLAG_CHAT, "Force a player to whistle");
    RegAdminCmd("ph_reload_models", ReloadModels, ADMFLAG_RCON, "Reload the modellist from the map config file.");
    RegAdminCmd("ph_debugmodels", Debug_ModelInfo, ADMFLAG_RCON, "Print model debug info to console.");
}

static void AddListeners() {
    HookConVarChange(cvar_HiderSpeed, OnChangeHiderSpeed);

    HookEvent("player_spawn", Event_OnPlayerSpawn);
    HookEvent("weapon_fire", Event_OnWeaponFire);
    HookEvent("player_death", Event_OnPlayerDeath);
    //HookEvent("player_death", Event_OnPlayerDeath_Pre, EventHookMode_Pre);
    HookEvent("round_start", Event_OnRoundStart);
    HookEvent("round_end", Event_OnRoundEnd);
    HookEvent("round_end", Event_OnRoundEnd_Pre, EventHookMode_Pre);
    HookEvent("player_team", Event_OnPlayerTeam);
    HookEvent("player_team", Event_OnPlayerTeam_Pre, EventHookMode_Pre);
    //HookEvent("teamchange_pending", Event_OnTeamChange);
    HookEvent("item_equip", Event_ItemEquip);

    AddCommandListener(Cmd_spec_next, "spec_next");
    AddCommandListener(Cmd_spec_prev, "spec_prev");
    AddCommandListener(Cmd_spec_player, "spec_player");
    AddCommandListener(Cmd_spec_mode, "spec_mode");
}

static void SetOffsets() {
    g_Freeze = FindSendPropOffs("CBasePlayer", "m_fFlags");
    g_flLaggedMovementValue = FindSendPropOffs("CCSPlayer", "m_flLaggedMovementValue");
}

static void LoadLang() {
    LoadTranslations("plugin.prophunt");
    LoadTranslations("common.phrases");
}

#include "prophunt/roundevents.sp"
#include "prophunt/mapevents.sp"
#include "prophunt/clientevents.sp"
#include "prophunt/commands.sp"
#include "prophunt/menus.sp"
#include "prophunt/models.sp"
#include "prophunt/spectate.sp"
#include "prophunt/teamevents.sp"


Entrefs try 1!

Entrefs try 1!

/**
* TheXeon
* ngs_autorestart.sp
*
* Files:
* addons/sourcemod/plugins/ngs_autorestart.smx
* cfg/sourcemod/autorestart.cfg
*
* Dependencies:
* sourcemod.inc, sdktools.inc, multicolors.inc, ngsutils.inc, ngsupdater.inc
*/
#pragma newdecls required
#pragma semicolon 1

#define CONTENT_URL "https://github.com/NGSNetwork/sm-plugins/raw/master/"

#include <sourcemod>
#include <sdktools>
#include <multicolors>
#include <ngsutils>
#include <ngsupdater>

public Plugin myinfo =
{
	name = "[NGS] Light Trails",
	author = "bSun Halt / TheXeon",
	description = "Gives a light trail",
	version = "1.0.0",
	url = "http://sourcemod.net"
}

static int SpriteTrail[MAXPLAYERS + 1] = {INVALID_ENT_REFERENCE, ...};

public void OnPluginStart()
{
	RegAdminCmd("sm_trail", Command_Trail, ADMFLAG_GENERIC, "sm_trail <color/hex>");
}

public void OnMapStart()
{
	PrecacheModel("materials/sprites/spotlight.vmt");
}

public void OnClientPutInServer(int client)
{
	SpriteTrail[client] = INVALID_ENT_REFERENCE;
}

public void OnClientDisconnect(int client)
{
	KillTrail(client);
}

public Action Command_Trail(int client, int args)
{
	if (args == 0)
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Usage: sm_trail <color/hex>");
		return Plugin_Handled;
	}

	char arg[64];
	char TrailColor[64];
	char ClientName[128];
	int trailcolornum;

	Format(ClientName, sizeof(ClientName), "customname_%i", client);
	DispatchKeyValue(client, "targetname", ClientName);
	int Trail = CreateEntityByName("env_spritetrail");
	DispatchKeyValue(Trail, "renderamt", "255");
	DispatchKeyValue(Trail, "rendermode", "1");
	DispatchKeyValue(Trail, "spritename", "materials/sprites/spotlight.vmt");
	DispatchKeyValue(Trail, "lifetime", "3.0");
	DispatchKeyValue(Trail, "startwidth", "8.0");
	DispatchKeyValue(Trail, "endwidth", "0.1");

	GetCmdArgString(arg, sizeof(arg));
	StrToLowerRemoveBlanks(arg, TrailColor, sizeof(TrailColor));

	KillTrail(client);

	if (StrEqual(TrailColor, "off", false))
	{
		if (IsValidEntity(Trail))
		{
			AcceptEntityInput(Trail, "Kill");
		}
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Your trail has been disabled!");
		return Plugin_Handled;
	}
	else if (MC_Trie != null && MC_Trie.GetValue(TrailColor, trailcolornum))
	{
		int rgbFromHex[3];
		rgbFromHex[0] = (trailcolornum >> 16) & 255;
		rgbFromHex[1] = (trailcolornum >> 8) & 255;
		rgbFromHex[2] = trailcolornum & 255;

		char rgbString[16];

		Format(rgbString, sizeof(rgbString), "%d %d %d", rgbFromHex[0], rgbFromHex[1], rgbFromHex[2]);
		DispatchKeyValue(Trail, "rendercolor", rgbString);
	}
	else
	{
		int decimalValue = StringToHex(TrailColor);
		int rgbFromHex[3];
		rgbFromHex[0] = (decimalValue >> 16) & 255;
		rgbFromHex[1] = (decimalValue >> 8) & 255;
		rgbFromHex[2] = decimalValue & 255;

		char rgbString[16];

		Format(rgbString, 16, "%d %d %d", rgbFromHex[0], rgbFromHex[1], rgbFromHex[2]);
		DispatchKeyValue(Trail, "rendercolor", rgbString);
	}

	DispatchSpawn(Trail);
	SpriteTrail[client] = EntIndexToEntRef(Trail);

	float CurrentOrigin[3];
	GetClientAbsOrigin(client, CurrentOrigin);
	CurrentOrigin[2] += 10.0;
	TeleportEntity(Trail, CurrentOrigin, NULL_VECTOR, NULL_VECTOR);
	SetVariantString(ClientName);

	AcceptEntityInput(Trail, "SetParent", -1, -1);
	AcceptEntityInput(Trail, "showsprite", -1, -1);

	CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} You've been given a %s trail!", arg);

	return Plugin_Handled;
}

stock void KillTrail(int client)
{
	if (SpriteTrail[client] == INVALID_ENT_REFERENCE) return;
	int entIndex = EntRefToEntIndex(SpriteTrail[client]);
	if (IsValidEntity(entIndex))
	{
		AcceptEntityInput(entIndex, "Kill");
		SpriteTrail[client] = INVALID_ENT_REFERENCE;
	}
}

public void OnPluginEnd()
{
	for (int i = 1; i <= MaxClients; i++)
	{
		int entIndex = EntRefToEntIndex(SpriteTrail[i]);
		if (IsValidClient(i) && IsValidEntity(entIndex))
			AcceptEntityInput(entIndex, "Kill");
	}
}

// Stock converted from:
// https://github.com/GabiGrin/hex-to-rgb-string/blob/master/index.js
stock int StringToHex(char hex[64])
{
	Regex hexPattern = new Regex("/^#(?:[0-9a-f]{3}){1,2}$/i");
	if (!hex || hexPattern.Match(hex) == -1)
	{
		delete hexPattern;
		return 0;
	}
	ReplaceString(hex, sizeof(hex), "#", "");
	ReplaceString(hex, sizeof(hex), "0x", "", false);
	char splitString[6];
	if (strlen(hex) == 3)
	{
		for (int i = 0; i < 5; i += 2)
		{
			splitString[i] = hex[i/2];
			splitString[i + 1] = hex[i/2];
		}
	}
	else
		strcopy(splitString, sizeof(splitString), hex);

	delete hexPattern;
	return StringToInt(splitString, 16);
}

// Credit to berni: https://forums.alliedmods.net/showpost.php?p=1008853&postcount=4
stock int StrToLowerRemoveBlanks(const char[] str, char[] dest, int destsize) {

	int n=0, x=0;
	while (str[n] != '\0' && x < (destsize - 1)) { // Make sure we are inside bounds

		int character = str[n++]; // Caching

		if (character == ' ') { // Am I nothing ?
			// Let's do nothing !
			continue;
		}
		else if (IsCharUpper(character)) { // Am I big ?
			character = CharToLower(character); // Big becomes low
		}

		dest[x++] = character; // Write into our new string
	}

	dest[x++] = '\0'; // Finalize with the end ( = always 0 for strings)

	return x; // return number of bytes written for later proove
}

/**
* TheXeon
* ngs_autorestart.sp
*
* Files:
* addons/sourcemod/plugins/ngs_autorestart.smx
* cfg/sourcemod/autorestart.cfg
*
* Dependencies:
* sourcemod.inc, sdktools.inc, multicolors.inc, ngsutils.inc, ngsupdater.inc
*/
#pragma newdecls required
#pragma semicolon 1

#define CONTENT_URL "https://github.com/NGSNetwork/sm-plugins/raw/master/"

#include <sourcemod>
#include <sdktools>
#include <multicolors>
#include <ngsutils>
#include <ngsupdater>

public Plugin myinfo =
{
	name = "[NGS] Light Trails",
	author = "bSun Halt / TheXeon",
	description = "Gives a light trail",
	version = "1.0.0",
	url = "http://sourcemod.net"
}

static int SpriteTrail[MAXPLAYERS + 1];

public void OnPluginStart()
{
	RegAdminCmd("sm_trail", Command_Trail, ADMFLAG_GENERIC, "sm_trail <color/hex>");
}

public void OnMapStart()
{
	PrecacheModel("materials/sprites/spotlight.vmt");
}

public void OnClientPutInServer(int client)
{
	SpriteTrail[client] = INVALID_ENT_REFERENCE;
}

public void OnClientDisconnect(int client)
{
	if (IsValidEntity(SpriteTrail[client]))
	{
		AcceptEntityInput(SpriteTrail[client], "Kill");
		SpriteTrail[client] = INVALID_ENT_REFERENCE;
	}
}

public Action Command_Trail(int client, int args)
{
	if (args == 0)
	{
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Usage: sm_trail <color/hex>");
		return Plugin_Handled;
	}

	char arg[64];
	char TrailColor[64];
//	char ClientName[128];
	int trailcolornum;

//	Format(ClientName, sizeof(ClientName), "customname_%i", client);
//	DispatchKeyValue(client, "targetname", ClientName);
	int Trail = CreateEntityByName("env_spritetrail");
	DispatchKeyValue(Trail, "renderamt", "255");
	DispatchKeyValue(Trail, "rendermode", "1");
	DispatchKeyValue(Trail, "spritename", "materials/sprites/spotlight.vmt");
	DispatchKeyValue(Trail, "lifetime", "3.0");
	DispatchKeyValue(Trail, "startwidth", "8.0");
	DispatchKeyValue(Trail, "endwidth", "0.1");

	GetCmdArgString(arg, sizeof(arg));
	StrToLowerRemoveBlanks(arg, TrailColor, sizeof(TrailColor));

	if (IsValidEntity(SpriteTrail[client]))
	{
		AcceptEntityInput(SpriteTrail[client], "Kill");
		SpriteTrail[client] = INVALID_ENT_REFERENCE;
	}

	if (StrEqual(TrailColor, "off", false))
	{
		if (IsValidEntity(Trail))
		{
			AcceptEntityInput(Trail, "Kill");
		}
		CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} Your trail has been disabled!");
		return Plugin_Handled;
	}
	else if (MC_Trie != null && MC_Trie.GetValue(TrailColor, trailcolornum))
	{
		int rgbFromHex[3];
		rgbFromHex[0] = (trailcolornum >> 16) & 255;
		rgbFromHex[1] = (trailcolornum >> 8) & 255;
		rgbFromHex[2] = trailcolornum & 255;

		char rgbString[16];

		Format(rgbString, sizeof(rgbString), "%d %d %d", rgbFromHex[0], rgbFromHex[1], rgbFromHex[2]);
		DispatchKeyValue(Trail, "rendercolor", rgbString);
	}
	else
	{
		int decimalValue = StringToHex(TrailColor);
		int rgbFromHex[3];
		rgbFromHex[0] = (decimalValue >> 16) & 255;
		rgbFromHex[1] = (decimalValue >> 8) & 255;
		rgbFromHex[2] = decimalValue & 255;

		char rgbString[16];

		Format(rgbString, 16, "%d %d %d", rgbFromHex[0], rgbFromHex[1], rgbFromHex[2]);
		DispatchKeyValue(Trail, "rendercolor", rgbString);
	}

	DispatchSpawn(Trail);
	SpriteTrail[client] = Trail;

	float CurrentOrigin[3];
	GetClientAbsOrigin(client, CurrentOrigin);
	CurrentOrigin[2] += 10.0;
	TeleportEntity(Trail, CurrentOrigin, NULL_VECTOR, NULL_VECTOR);
//	SetVariantString(ClientName);

	AcceptEntityInput(Trail, "SetParent", -1, -1);
	AcceptEntityInput(Trail, "showsprite", -1, -1);

	CReplyToCommand(client, "{GREEN}[SM]{DEFAULT} You've been given a %s trail!", arg);

	return Plugin_Handled;
}

public void OnPluginEnd()
{
	for (int i = 1; i <= MaxClients; i++)
		if (IsValidClient(i) && IsValidEntity(SpriteTrail[i]))
			AcceptEntityInput(SpriteTrail[i], "Kill");
}

// Stock converted from:
// https://github.com/GabiGrin/hex-to-rgb-string/blob/master/index.js
stock int StringToHex(char hex[64])
{
	Regex hexPattern = new Regex("/^#(?:[0-9a-f]{3}){1,2}$/i");
	if (!hex || hexPattern.Match(hex) == -1)
	{
		delete hexPattern;
		return 0;
	}
	ReplaceString(hex, sizeof(hex), "#", "");
	ReplaceString(hex, sizeof(hex), "0x", "", false);
	char splitString[6];
	if (strlen(hex) == 3)
	{
		for (int i = 0; i < 5; i += 2)
		{
			splitString[i] = hex[i/2];
			splitString[i + 1] = hex[i/2];
		}
	}
	else
		strcopy(splitString, sizeof(splitString), hex);

	delete hexPattern;
	return StringToInt(splitString, 16);
}

// Credit to berni: https://forums.alliedmods.net/showpost.php?p=1008853&postcount=4
stock int StrToLowerRemoveBlanks(const char[] str, char[] dest, int destsize) {

	int n=0, x=0;
	while (str[n] != '\0' && x < (destsize - 1)) { // Make sure we are inside bounds

		int character = str[n++]; // Caching

		if (character == ' ') { // Am I nothing ?
			// Let's do nothing !
			continue;
		}
		else if (IsCharUpper(character)) { // Am I big ?
			character = CharToLower(character); // Big becomes low
		}

		dest[x++] = character; // Write into our new string
	}

	dest[x++] = '\0'; // Finalize with the end ( = always 0 for strings)

	return x; // return number of bytes written for later proove
}


RM Smooth witch damage

RM Smooth witch damage

source now in promod repo

#include <sourcemod>
#include <sdkhooks>
#pragma semicolon 1

public Plugin:myinfo =
{
	name = "Smooth witch damage",
	author = "Darkid",
	description = "Smooths out the damage taken from a witch while incapped.",
	version = "1.7",
	url = "https://github.com/jbzdarkid/Random/blob/master/smooth_witch_damage.sp"
}

new const bool:DEBUG = true;

new Float:witchDamage;
new Handle:deadWitches;
new Handle:engagedWitches;
new Float:startTick;
new bool:lateLoad = false;

public APLRes:AskPluginLoad2(Handle:plugin, bool:late, String:error[], errMax) {
	lateLoad = late;
	return APLRes_Success;
}

public OnPluginStart() {
	if (lateLoad) {
		for (new client=1; client<=MaxClients; client++) {
			if (!IsClientInGame(client)) continue;
			OnClientPostAdminCheck(client);
		}
	}

	decl String:witchDamageString[64];
	GetConVarString(FindConVar("z_witch_damage_per_kill_hit"), witchDamageString, sizeof(witchDamageString));
	HookConVarChange(FindConVar("z_witch_damage_per_kill_hit"), OnWitchDamageChange);
	witchDamage = 1.0*StringToInt(witchDamageString);

	deadWitches = CreateArray(64);
	engagedWitches = CreateArray(64);

	HookEvent("round_start", round_start);
	HookEvent("witch_killed", witch_killed);
	startTick = 0.0;
}

public OnWitchDamageChange(Handle:cvar, const String:oldVal[], const String:newVal[]) {
	witchDamage = 1.0*StringToInt(newVal);
}

public OnClientPostAdminCheck(client) {
	SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
}

public round_start(Handle:event, const String:name[], bool:dontBroadcast) {
	ClearArray(deadWitches);
	ClearArray(engagedWitches);
}

bool:IsWitch(entity) {
	if (entity <= 0 || !IsValidEntity(entity) || !IsValidEdict(entity)) return false;
	decl String:className[8];
	GetEdictClassname(entity, className, sizeof(className));
	return strcmp(className, "witch") == 0;
}

public Action:OnTakeDamage(victim, &attacker, &inflictor, &Float:damage, &damageType) {
	if (victim <= 0 || victim > MaxClients || !IsClientInGame(victim)) return Plugin_Continue;
	if (!IsWitch(attacker)) return Plugin_Continue;
	// A hack. We assume the first scratch isn't the same damage as an incap scratch.
	// It's also worth noting that this prevents infinite recursion, since WitchScratch reduces the damage dealt.
	if (damage != witchDamage) return Plugin_Continue;
	if (FindValueInArray(engagedWitches, attacker) != -1) return Plugin_Stop;
	startTick = GetEngineTime();
	PushArrayCell(engagedWitches, attacker);
	new Handle:data = CreateArray(64);
	PushArrayCell(data, victim);
	PushArrayCell(data, attacker);
	PushArrayCell(data, inflictor);
	PushArrayCell(data, damageType);
	// The witch scratch animation is every 1/4 second. Repeat until survivor DIES.
	CreateTimer(0.25, WitchScratch, data, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);

	damage = witchDamage/2.5;
	if (DEBUG) PrintToChatAll("[%02.3f] Player %d took %3f damage from witch %d/%d.", GetEngineTime()-startTick, victim, witchDamage/2.5, attacker, inflictor);
	return Plugin_Continue;
}

public Action:WitchScratch(Handle:timer, any:data) {
	new victim = GetArrayCell(data, 0);
	// If defibrillator_use_time is less than .25, this may bug out.
	new attacker = GetArrayCell(data, 1);
	if (!IsPlayerAlive(victim)) {
		new index = FindValueInArray(engagedWitches, attacker);
		RemoveFromArray(engagedWitches, index);
		return Plugin_Stop;
	}
	new index = FindValueInArray(deadWitches, attacker);
	if (index != -1) {
		RemoveFromArray(deadWitches, index);
		index = FindValueInArray(engagedWitches, attacker);
		if (index != -1) RemoveFromArray(engagedWitches, index);
		return Plugin_Stop;
	}
	new inflictor = GetArrayCell(data, 2);
	new damageType = GetArrayCell(data, 3);
	SDKHooks_TakeDamage(victim, attacker, inflictor, witchDamage/2.5, damageType);
	if (DEBUG) PrintToChatAll("[%02.3f] Player %d took %3f damage from witch %d/%d", GetEngineTime()-startTick, victim, witchDamage/2.5, attacker, inflictor);
	return Plugin_Continue;
}

public witch_killed(Handle:event, const String:name[], bool:dontBroadcast) {
	new witch = GetEventInt(event, "witchid");
	PushArrayCell(deadWitches, witch);
}


Fix Tag Mismatch SourceSleuth

Fix Tag Mismatch SourceSleuth

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 Sarabveer Singh <me@sarabveer.me>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebans>

#define PLUGIN_VERSION "(SB++) 1.5.5-dev"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4

//- Handles -//
new Handle:hDatabase = INVALID_HANDLE;
new Handle:g_hAllowedArray = INVALID_HANDLE;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;

//- Bools -//
new bool:CanUseSourcebans = false;

public Plugin:myinfo =
{
	name = "SourceSleuth",
	author = "ecca, Sarabveer(VEER™)",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sarabveer.github.io/SourceBans-Fork/"
};

public OnPluginStart()
{
	LoadTranslations("sourcesleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only", 0, true, 1.0, true, 4.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )", 0);
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb", 0);
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act", 0);
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", 0, true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", 0, true, 0.0, true, 1.0);

	g_hAllowedArray = CreateArray(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	SQL_TConnect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans");
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = true;
	}
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = false;
	}
}

public SQL_OnConnect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = hndl;
	}
}

public Action:ReloadListCallBack(client, args)
{
	ClearArray(g_hAllowedArray);

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "[SourceSleuth] WhiteList has been reloaded!");
	}

	return Plugin_Continue;
}

public OnClientPostAdminCheck(client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		new String:steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (FindStringInArray(g_hAllowedArray, steamid) == -1)
		{
			new String:IP[32], String:Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			new String:query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND ends > %d", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0);

			new Handle:datapack = CreateDataPack();

			WritePackCell(datapack, GetClientUserId(client));
			WritePackString(datapack, steamid);
			WritePackString(datapack, IP);
			ResetPack(datapack);

			SQL_TQuery(hDatabase, SQL_CheckHim, query, datapack);
		}
	}
}

public SQL_CheckHim(Handle:owner, Handle:hndl, const String:error[], any:datapack)
{
	new client;
	decl String:steamid[32], String:IP[32];

	if (datapack != INVALID_HANDLE)
	{
		client = GetClientOfUserId(ReadPackCell(datapack));
		ReadPackString(datapack, steamid, sizeof(steamid));
		ReadPackString(datapack, IP, sizeof(IP));
		CloseHandle(datapack);
	}

	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (SQL_FetchRow(hndl))
	{
		new TotalBans = SQL_GetRowCount(hndl);

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					new length = SQL_FetchInt(hndl, 6);
					new time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					new time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					new length = SQL_FetchInt(hndl, 6);

					new time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("[SourceSleuth] %t", "sourcesleuth_admintext", client, steamid, IP);
				}
			}
		}
	}
}

stock BanPlayer(client, time)
{
	decl String:Reason[255];
	Format(Reason, sizeof(Reason), "[SourceSleuth] %t", "sourcesleuth_banreason");
	SBBanPlayer(0, client, time, Reason);
}

PrintToAdmins(const String:format[], any:...)
{
	new String:g_Buffer[256];

	for (new i = 1; i <= MaxClients; i++)
	{
		if (CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN) && IsClientInGame(i))
		{
			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public LoadWhiteList()
{
	decl String:path[PLATFORM_MAX_PATH], String:line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcesleuth_whitelist.cfg");

	new Handle:fileHandle = OpenFile(path, "r");

	while (!IsEndOfFile(fileHandle) && ReadFileLine(fileHandle, line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		PushArrayString(g_hAllowedArray, line);
	}

	CloseHandle(fileHandle);
}

// *************************************************************************
//  This file is part of SourceBans++.
//
//  Copyright (C) 2014-2016 Sarabveer Singh <me@sarabveer.me>
//
//  SourceBans++ is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans++ is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with SourceBans++. If not, see <http://www.gnu.org/licenses/>.
//
//  This file is based off work covered by the following copyright(s):
//
//   SourceSleuth 1.3 fix
//   Copyright (C) 2013-2015 ecca
//   Licensed under GNU GPL version 3, or later.
//   Page: <https://forums.alliedmods.net/showthread.php?p=1818793> - <https://github.com/ecca/SourceMod-Plugins>
//
// *************************************************************************

#pragma semicolon 1
#include <sourcemod>
#undef REQUIRE_PLUGIN
#include <sourcebans>

#define PLUGIN_VERSION "(SB++) 1.5.5-dev"

#define LENGTH_ORIGINAL 1
#define LENGTH_CUSTOM 2
#define LENGTH_DOUBLE 3
#define LENGTH_NOTIFY 4

//- Handles -//
new Handle:hDatabase = INVALID_HANDLE;
new Handle:g_hAllowedArray = INVALID_HANDLE;

//- ConVars -//
ConVar g_cVar_actions;
ConVar g_cVar_banduration;
ConVar g_cVar_sbprefix;
ConVar g_cVar_bansAllowed;
ConVar g_cVar_bantype;
ConVar g_cVar_bypass;

//- Bools -//
new bool:CanUseSourcebans = false;

public Plugin:myinfo =
{
	name = "SourceSleuth",
	author = "ecca, Sarabveer(VEER™)",
	description = "Useful for TF2 servers. Plugin will check for banned ips and ban the player.",
	version = PLUGIN_VERSION,
	url = "https://sarabveer.github.io/SourceBans-Fork/"
};

public OnPluginStart()
{
	LoadTranslations("sourcesleuth.phrases");

	CreateConVar("sm_sourcesleuth_version", PLUGIN_VERSION, "SourceSleuth plugin version", FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);

	g_cVar_actions = CreateConVar("sm_sleuth_actions", "3", "Sleuth Ban Type: 1 - Original Length, 2 - Custom Length, 3 - Double Length, 4 - Notify Admins Only", true, 1.0, true, 4.0);
	g_cVar_banduration = CreateConVar("sm_sleuth_duration", "0", "Required: sm_sleuth_actions 1: Bantime to ban player if we got a match (0 = permanent (defined in minutes) )");
	g_cVar_sbprefix = CreateConVar("sm_sleuth_prefix", "sb", "Prexfix for sourcebans tables: Default sb");
	g_cVar_bansAllowed = CreateConVar("sm_sleuth_bansallowed", "0", "How many active bans are allowed before we act");
	g_cVar_bantype = CreateConVar("sm_sleuth_bantype", "0", "0 - ban all type of lengths, 1 - ban only permanent bans", true, 0.0, true, 1.0);
	g_cVar_bypass = CreateConVar("sm_sleuth_adminbypass", "0", "0 - Inactivated, 1 - Allow all admins with ban flag to pass the check", true, 0.0, true, 1.0);

	g_hAllowedArray = CreateArray(256);

	AutoExecConfig(true, "Sm_SourceSleuth");

	SQL_TConnect(SQL_OnConnect, "sourcebans");

	RegAdminCmd("sm_sleuth_reloadlist", ReloadListCallBack, ADMFLAG_ROOT);

	LoadWhiteList();
}

public OnAllPluginsLoaded()
{
	CanUseSourcebans = LibraryExists("sourcebans");
}

public OnLibraryAdded(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = true;
	}
}

public OnLibraryRemoved(const String:name[])
{
	if (StrEqual("sourcebans", name))
	{
		CanUseSourcebans = false;
	}
}

public SQL_OnConnect(Handle:owner, Handle:hndl, const String:error[], any:data)
{
	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database connection error: %s", error);
	}
	else
	{
		hDatabase = hndl;
	}
}

public Action:ReloadListCallBack(client, args)
{
	ClearArray(g_hAllowedArray);

	LoadWhiteList();

	LogMessage("%L reloaded the whitelist", client);

	if (client != 0)
	{
		PrintToChat(client, "[SourceSleuth] WhiteList has been reloaded!");
	}

	return Plugin_Continue;
}

public OnClientPostAdminCheck(client)
{
	if (CanUseSourcebans && !IsFakeClient(client))
	{
		new String:steamid[32];
		GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));

		if (g_cVar_bypass.BoolValue && CheckCommandAccess(client, "sleuth_admin", ADMFLAG_BAN, false))
		{
			return;
		}

		if (FindStringInArray(g_hAllowedArray, steamid) == -1)
		{
			new String:IP[32], String:Prefix[64];
			GetClientIP(client, IP, sizeof(IP));

			g_cVar_sbprefix.GetString(Prefix, sizeof(Prefix));

			new String:query[1024];

			FormatEx(query, sizeof(query), "SELECT * FROM %s_bans WHERE ip='%s' AND RemoveType IS NULL AND ends > %d", Prefix, IP, g_cVar_bantype.IntValue == 0 ? GetTime() : 0);

			new Handle:datapack = CreateDataPack();

			WritePackCell(datapack, GetClientUserId(client));
			WritePackString(datapack, steamid);
			WritePackString(datapack, IP);
			ResetPack(datapack);

			SQL_TQuery(hDatabase, SQL_CheckHim, query, datapack);
		}
	}
}

public SQL_CheckHim(Handle:owner, Handle:hndl, const String:error[], any:datapack)
{
	new client;
	decl String:steamid[32], String:IP[32];

	if (datapack != INVALID_HANDLE)
	{
		client = GetClientOfUserId(ReadPackCell(datapack));
		ReadPackString(datapack, steamid, sizeof(steamid));
		ReadPackString(datapack, IP, sizeof(IP));
		CloseHandle(datapack);
	}

	if (hndl == INVALID_HANDLE)
	{
		LogError("SourceSleuth: Database query error: %s", error);
		return;
	}

	if (SQL_FetchRow(hndl))
	{
		new TotalBans = SQL_GetRowCount(hndl);

		if (TotalBans > g_cVar_bansAllowed.IntValue)
		{
			switch (g_cVar_actions.IntValue)
			{
				case LENGTH_ORIGINAL:
				{
					new length = SQL_FetchInt(hndl, 6);
					new time = length * 60;

					BanPlayer(client, time);
				}
				case LENGTH_CUSTOM:
				{
					new time = g_cVar_banduration.IntValue;
					BanPlayer(client, time);
				}
				case LENGTH_DOUBLE:
				{
					new length = SQL_FetchInt(hndl, 6);

					new time = 0;

					if (length != 0)
					{
						time = length / 60 * 2;
					}

					BanPlayer(client, time);
				}
				case LENGTH_NOTIFY:
				{
					/* Notify Admins when a client with an ip on the bans list connects */
					PrintToAdmins("[SourceSleuth] %t", "sourcesleuth_admintext", client, steamid, IP);
				}
			}
		}
	}
}

stock BanPlayer(client, time)
{
	decl String:Reason[255];
	Format(Reason, sizeof(Reason), "[SourceSleuth] %t", "sourcesleuth_banreason");
	SBBanPlayer(0, client, time, Reason);
}

PrintToAdmins(const String:format[], any:...)
{
	new String:g_Buffer[256];

	for (new i = 1; i <= MaxClients; i++)
	{
		if (CheckCommandAccess(i, "sm_sourcesleuth_printtoadmins", ADMFLAG_BAN) && IsClientInGame(i))
		{
			VFormat(g_Buffer, sizeof(g_Buffer), format, 2);

			PrintToChat(i, "%s", g_Buffer);
		}
	}
}

public LoadWhiteList()
{
	decl String:path[PLATFORM_MAX_PATH], String:line[256];

	BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/sourcesleuth_whitelist.cfg");

	new Handle:fileHandle = OpenFile(path, "r");

	while (!IsEndOfFile(fileHandle) && ReadFileLine(fileHandle, line, sizeof(line)))
	{
		ReplaceString(line, sizeof(line), "\n", "", false);

		PushArrayString(g_hAllowedArray, line);
	}

	CloseHandle(fileHandle);
}