removed test code

removed test code

// *************************************************************************
//  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(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 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;
}


Added support for CBaseProjectile. I found the offset by looping through all values until it worked.

#include <sdktools>
#include <sdkhooks>

public Plugin:myinfo =
{
  name = "Weapon function times",
  author = "darkid",
  description = "Calculates the speed of projectiles, and firing speed of weapons. Created for the TF2 Wiki.",
  version = "3.2",
}

new deferredEntity = -1;
new deferredEntity2 = -1;

public OnPluginStart() {
  RegConsoleCmd("sm_setspell", SetSpell);
}

public Action:SetSpell(client, args) {
  if (args == 0) {
    ReplyToCommand(client, "Usage: sm_setspell <id>\n\
0\tFireball\n\
1\tBall O' Bats\n\
2\tUber Heal\n\
3\tPumpkin MIRV\n\
4\tBlast Jump\n\
5\tStealth\n\
6\tTeleport\n\
7\tBall O' Lightning\n\
8\tMinify\n\
9\tMeteor Storm\n\
10\tSummon MONOCULUS\n\
11\tSummon Skeletons (Spawns 3)\n\
12\tBoxing Rocket\n\
13\tB.A.S.E. Jump\n\
14\tUber Heal (Bumper Cars version)\n\
15\tBombinomicon Head\n\
");
  }
  new String:arg[16];
  GetCmdArg(1, arg, sizeof(arg));
  new spellbook = FindEntityByClassname(0, "tf_weapon_spellbook");
  SetEntProp(spellbook, Prop_Send, "m_iSelectedSpellIndex", StringToInt(arg));
  SetEntProp(spellbook, Prop_Send, "m_iSpellCharges", 999999);
  return Plugin_Handled;
}

public OnEntityCreated(entity, const String:classname[]) {
  deferredEntity = entity;
}

public Action:TF2_CalcIsAttackCritical(client, weapon, String:weaponname[], &bool:result) {
  deferredEntity2 = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
}

public Float:GetSpeed(Float:velocity[3]) {
  return SquareRoot(Pow(velocity[0], 2.0)+Pow(velocity[1], 2.0)+Pow(velocity[2], 2.0));
}

public OnGameFrame() {
  if (deferredEntity2 != -1 && false) {
    new Float:enginetime = GetGameTime();
    PrintToChat(1, "Next Primary Attack: %f", GetEntPropFloat(deferredEntity2, Prop_Send, "m_flNextPrimaryAttack") - enginetime);
    PrintToChat(1, "Next Secondary Attack: %f", GetEntPropFloat(deferredEntity2, Prop_Send, "m_flNextSecondaryAttack") - enginetime);
    PrintToChat(1, "Ammo Loaded: %d", GetEntProp(deferredEntity2, Prop_Send, "m_iClip1")); // Not working for minigun?
    deferredEntity2 = -1;
  }
  if (deferredEntity != -1 && IsValidEntity(deferredEntity)) {
    decl Float:velocity[3];
    new offset = GetEntSendPropOffs(deferredEntity, "m_vInitialVelocity");
    if (offset == -1) { // Not a projectile, since it doesn't have velocity property
      offset = 572; // Try using the magic number for CBaseProjectile
    }

    static Float:averageSpeed = 0.0;
    static numTrials = 0;
    static Float:minSpeed = 9999999.9;
    static Float:maxSpeed = 0.0;

    GetEntDataVector(deferredEntity, offset, velocity);
    new Float:speed = GetSpeed(velocity);
    if (speed > 0.0) {
      PrintToChat(1, "Projectile speed: %f", speed);
      averageSpeed += speed;
      numTrials++;
      if (speed < minSpeed) minSpeed = speed;
      if (speed > maxSpeed) maxSpeed = speed;
      PrintToChat(1, "N: %d AVG: %f MIN: %f MAX: %f", numTrials, averageSpeed/numTrials, minSpeed, maxSpeed);
    }
    deferredEntity = -1;
  }
}

#include <sdktools>
#include <sdkhooks>

public Plugin:myinfo =
{
  name = "Weapon function times",
  author = "darkid",
  description = "Calculates the speed of projectiles, and firing speed of weapons. Created for the TF2 Wiki.",
  version = "3.2",
}

new deferredEntity = -1;
new deferredEntity2 = -1;

public OnPluginStart() {
  RegConsoleCmd("sm_setspell", SetSpell);
}

public Action:SetSpell(client, args) {
  if (args == 0) {
    ReplyToCommand(client, "Usage: sm_setspell <id>\n\
0\tFireball\n\
1\tBall O' Bats\n\
2\tUber Heal\n\
3\tPumpkin MIRV\n\
4\tBlast Jump\n\
5\tStealth\n\
6\tTeleport\n\
7\tBall O' Lightning\n\
8\tMinify\n\
9\tMeteor Storm\n\
10\tSummon MONOCULUS\n\
11\tSummon Skeletons (Spawns 3)\n\
12\tBoxing Rocket\n\
13\tB.A.S.E. Jump\n\
14\tUber Heal (Bumper Cars version)\n\
15\tBombinomicon Head\n\
");
  }
  new String:arg[16];
  GetCmdArg(1, arg, sizeof(arg));
  new spellbook = FindEntityByClassname(0, "tf_weapon_spellbook");
  SetEntProp(spellbook, Prop_Send, "m_iSelectedSpellIndex", StringToInt(arg));
  SetEntProp(spellbook, Prop_Send, "m_iSpellCharges", 999999);
  return Plugin_Handled;
}

public OnEntityCreated(entity, const String:classname[]) {
  deferredEntity = entity;
}

public Action:TF2_CalcIsAttackCritical(client, weapon, String:weaponname[], &bool:result) {
  deferredEntity2 = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
}

public Float:GetSpeed(Float:velocity[3]) {
  return SquareRoot(Pow(velocity[0], 2.0)+Pow(velocity[1], 2.0)+Pow(velocity[2], 2.0));
}

public OnGameFrame() {
  if (deferredEntity2 != -1) {
    new Float:enginetime = GetGameTime();
    PrintToChat(1, "Next Primary Attack: %f", GetEntPropFloat(deferredEntity2, Prop_Send, "m_flNextPrimaryAttack") - enginetime);
    PrintToChat(1, "Next Secondary Attack: %f", GetEntPropFloat(deferredEntity2, Prop_Send, "m_flNextSecondaryAttack") - enginetime);
    PrintToChat(1, "Ammo Loaded: %d", GetEntProp(deferredEntity2, Prop_Send, "m_iClip1")); // Not working for minigun?
    deferredEntity2 = -1;
  }
  if (deferredEntity != -1 && IsValidEntity(deferredEntity)) {
    new offset = GetEntSendPropOffs(deferredEntity, "m_vInitialVelocity");
    if (offset == -1) { // Not a projectile, since it doesn't have velocity.
      deferredEntity = -1;
      return;
    }

    static Float:averageSpeed = 0.0;
    static numTrials = 0;
    static Float:minSpeed = 9999999.9;
    static Float:maxSpeed = 0.0;

    decl Float:velocity[3];
    GetEntDataVector(deferredEntity, offset, velocity);
    new Float:speed = GetSpeed(velocity);
    if (speed == 0.0) return;
    PrintToChat(1, "Projectile speed: %f", speed);
    averageSpeed += speed;
    numTrials++;
    if (speed < minSpeed) minSpeed = speed;
    if (speed > maxSpeed) maxSpeed = speed;
    PrintToChat(1, "N: %d AVG: %f MIN: %f MAX: %f", numTrials, averageSpeed/numTrials, minSpeed, maxSpeed);
    deferredEntity = -1;
  }
}