Description for Invalid Target

Description for Invalid Target

Add INVALID_TARGET constant. Add ReplyToCommand() to let sender know we couldn't find the target.

// *************************************************************************
//  This file is part of SourceBans-Fork.
//
//  Copyright (C) 2014-2015 Sarabveer Singh <sarabveer@sarabveer.me>
//
//  SourceBans-Fork is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans-Fork 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 Affero General Public License
//  along with SourceBans-Fork. If not, see <http://www.gnu.org/licenses/>.
//
//  This file incorporates work 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 "SBF-1.5.2F-R2-dev"
#define LISTBANS_USAGE "sm_listsbbans <#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;

public Plugin:myinfo =
{
	name = "SourceBans Checker",
	author = "psychonic, Ca$h Munny, Sarabveer(VEER™)",
	description = "Notifies admins of prior bans from Sourcebans upon player connect.",
	version = VERSION,
	url = "http://www.nicholashastings.com"
};

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

	CreateConVar("sbchecker_version", VERSION, "", FCVAR_NOTIFY);
	RegAdminCmd("sm_listsbbans", 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)
	{
		PrintToBanAdmins("\x04[SourceBans]\x01 Warning: Player \"%N\" has %d previous SB 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))
		{
			SQL_FetchString(hndl, 1, bannedby, sizeof(bannedby));
			new len = SQL_FetchSize(hndl, 1);
			if (len > sizeof(bannedby)-1)
			{
				reason[sizeof(bannedby)-4] = '.';
				reason[sizeof(bannedby)-3] = '.';
				reason[sizeof(bannedby)-2] = '.';
			}
			else
			{
				for (new i = len; i < sizeof(bannedby)-1; i++)
				{
					bannedby[i] = ' ';
				}
			}
		}

		// NOT NULL
		new length = SQL_FetchInt(hndl, 3);
		if (length == 0)
		{
			strcopy(lenstring, sizeof(lenstring), "Permanent ");
		}
		else
		{
			new len = IntToString(length, lenstring, sizeof(lenstring));
			if (len < sizeof(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
		SQL_FetchString(hndl, 4, reason, sizeof(reason));
		new len = SQL_FetchSize(hndl, 4);
		if (len > sizeof(reason)-1)
		{
			reason[sizeof(reason)-4] = '.';
			reason[sizeof(reason)-3] = '.';
			reason[sizeof(reason)-2] = '.';
		}
		else
		{
			for (new i = len; i < sizeof(reason)-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];
		if (SMC_GetErrorString(err, buffer, sizeof(buffer)))
		{
			PrintToServer(buffer);
		}
		else
		{
			PrintToServer("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-Fork.
//
//  Copyright (C) 2014-2015 Sarabveer Singh <sarabveer@sarabveer.me>
//
//  SourceBans-Fork is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published by
//  the Free Software Foundation, per version 3 of the License.
//
//  SourceBans-Fork 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 Affero General Public License
//  along with SourceBans-Fork. If not, see <http://www.gnu.org/licenses/>.
//
//  This file incorporates work 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 "SBF-1.5.2F-R2-dev"
#define LISTBANS_USAGE "sm_listsbbans <#userid|name> - Lists a user's prior bans from Sourcebans"

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

public Plugin:myinfo =
{
	name = "SourceBans Checker",
	author = "psychonic, Ca$h Munny, Sarabveer(VEER™)",
	description = "Notifies admins of prior bans from Sourcebans upon player connect.",
	version = VERSION,
	url = "http://www.nicholashastings.com"
};

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

	CreateConVar("sbchecker_version", VERSION, "", FCVAR_NOTIFY);
	RegAdminCmd("sm_listsbbans", 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)
	{
		PrintToBanAdmins("\x04[SourceBans]\x01 Warning: Player \"%N\" has %d previous SB 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 == -1)
	{
		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))
		{
			SQL_FetchString(hndl, 1, bannedby, sizeof(bannedby));
			new len = SQL_FetchSize(hndl, 1);
			if (len > sizeof(bannedby)-1)
			{
				reason[sizeof(bannedby)-4] = '.';
				reason[sizeof(bannedby)-3] = '.';
				reason[sizeof(bannedby)-2] = '.';
			}
			else
			{
				for (new i = len; i < sizeof(bannedby)-1; i++)
				{
					bannedby[i] = ' ';
				}
			}
		}

		// NOT NULL
		new length = SQL_FetchInt(hndl, 3);
		if (length == 0)
		{
			strcopy(lenstring, sizeof(lenstring), "Permanent ");
		}
		else
		{
			new len = IntToString(length, lenstring, sizeof(lenstring));
			if (len < sizeof(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
		SQL_FetchString(hndl, 4, reason, sizeof(reason));
		new len = SQL_FetchSize(hndl, 4);
		if (len > sizeof(reason)-1)
		{
			reason[sizeof(reason)-4] = '.';
			reason[sizeof(reason)-3] = '.';
			reason[sizeof(reason)-2] = '.';
		}
		else
		{
			for (new i = len; i < sizeof(reason)-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];
		if (SMC_GetErrorString(err, buffer, sizeof(buffer)))
		{
			PrintToServer(buffer);
		}
		else
		{
			PrintToServer("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;
}


Fixed a bug with infinite looping on error, added a command to give spells

#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;
  }
}

#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.1",
}

new deferredEntity = -1;
new deferredEntity2 = -1;
new Float:averageSpeed = 0.0;
new numTrials = 0;
new Float:minSpeed = 9999999.9;
new Float:maxSpeed = 0.0;

public OnEntityCreated(entity, const String:classname[]) {
  if (StrEqual(classname, "instanced_scripted_scene") ||
      StrEqual(classname, "env_spritetrail") ||
      StrEqual(classname, "tf_wearable")
  ) return; // Eliminating some extraneous entities
  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) {
    decl Float:velocity[3];
    GetEntPropVector(deferredEntity, Prop_Send, "m_vInitialVelocity", velocity);
    new Float:speed = GetSpeed(velocity);
    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;
  }
}