/**
 * =============================================================================
 * Left 4 Dead Stocks Library (C)2011-2012 Buster "Mr. Zero" Nielsen
 * Syntax Update and merge into "Left 4 DHooks Direct" (C) 2025 "SilverShot"
 * =============================================================================
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License, version 3.0, as
 * published by the Free Software Foundation.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As a special exception, AlliedModders LLC gives you permission to link the
 * code of this program (as well as its derivative works) to "Half-Life 2,"
 * the "Source Engine," the "SourcePawn JIT," and any Game MODs that run on
 * software by the Valve Corporation.  You must obey the GNU General Public
 * License in all respects for all other code used.  Additionally,
 * AlliedModders LLC grants this exception to all derivative works.
 * AlliedModders LLC defines further exceptions, found in LICENSE.txt
 * (as of this writing, version JULY-31-2007), or
 * <http://www.sourcemod.net/license.php>.
 */

#if defined _l4d_stocks_included
 #endinput
#endif
#define _l4d_stocks_included

#pragma newdecls required

#tryinclude <left4dhooks_anim>
#tryinclude <left4dhooks_silver>
#tryinclude <left4dhooks_lux_library>





// ====================================================================================================
// VARIOUS STOCKS: "l4d_stocks.inc" by "Mr. Zero"
// ====================================================================================================

#include <sdktools>

/* Spawn state bit flags */
#define L4D_SPAWNFLAG_CANSPAWN			(0 << 0)
#define L4D_SPAWNFLAG_DISABLED			(1 << 0)
#define L4D_SPAWNFLAG_WAITFORSURVIVORS	(1 << 1)
#define L4D_SPAWNFLAG_WAITFORFINALE		(1 << 2)
#define L4D_SPAWNFLAG_WAITFORTANKTODIE	(1 << 3)
#define L4D_SPAWNFLAG_SURVIVORESCAPED	(1 << 4)
#define L4D_SPAWNFLAG_DIRECTORTIMEOUT	(1 << 5)
#define L4D_SPAWNFLAG_WAITFORNEXTWAVE	(1 << 6)
#define L4D_SPAWNFLAG_CANBESEEN			(1 << 7)
#define L4D_SPAWNFLAG_TOOCLOSE			(1 << 8)
#define L4D_SPAWNFLAG_RESTRICTEDAREA	(1 << 9)
#define L4D_SPAWNFLAG_BLOCKED			(1 << 10)

/* Weapon upgrade bit flags */
#define L4D2_WEPUPGFLAG_NONE			(0 << 0)
#define L4D2_WEPUPGFLAG_INCENDIARY		(1 << 0)
#define L4D2_WEPUPGFLAG_EXPLOSIVE		(1 << 1)
#define L4D2_WEPUPGFLAG_LASER			(1 << 2)

/* Instructor Hint bit flags */
#define L4D2_IHFLAG_NONE				(0 << 0)
#define L4D2_IHFLAG_PULSE_SLOW			(1 << 0)
#define L4D2_IHFLAG_PULSE_FAST			(1 << 1)
#define L4D2_IHFLAG_PULSE_URGENT		(1 << 2)
#define L4D2_IHFLAG_ALPHA_SLOW			(1 << 3)
#define L4D2_IHFLAG_ALPHA_FAST			(1 << 4)
#define L4D2_IHFLAG_ALPHA_URGENT		(1 << 5)
#define L4D2_IHFLAG_SHAKE_NARROW		(1 << 6)
#define L4D2_IHFLAG_SHAKE_WIDE			(1 << 7)
#define L4D2_IHFLAG_STATIC				(1 << 8)

/* Survivor intensity -- Equal or less */
#define L4D_SURVIVORINTENSITY_MILD		0.25
#define L4D_SURVIVORINTENSITY_MODERATE	0.50
#define L4D_SURVIVORINTENSITY_HIGH		0.75
#define L4D_SURVIVORINTENSITY_EXTREME	1.00

enum L4DTeam
{
	L4DTeam_Unassigned				= 0,
	L4DTeam_Spectator				= 1,
	L4DTeam_Survivor				= 2,
	L4DTeam_Infected				= 3
}

enum L4DWeaponSlot
{
	L4DWeaponSlot_Primary			= 0,
	L4DWeaponSlot_Secondary			= 1,
	L4DWeaponSlot_Grenade			= 2,
	L4DWeaponSlot_FirstAid			= 3,
	L4DWeaponSlot_Pills				= 4
}

enum L4D2GlowType
{
	L4D2Glow_None					= 0,
	L4D2Glow_OnUse					= 1,
	L4D2Glow_OnLookAt				= 2,
	L4D2Glow_Constant				= 3
}

enum L4D1ZombieClassType
{
	L4D1ZombieClass_Smoker			= 1,
	L4D1ZombieClass_Boomer			= 2,
	L4D1ZombieClass_Hunter			= 3,
	L4D1ZombieClass_Witch			= 4,
	L4D1ZombieClass_Tank			= 5,
	L4D1ZombieClass_NotInfected		= 6
}

enum L4D2ZombieClassType
{
	L4D2ZombieClass_Smoker			= 1,
	L4D2ZombieClass_Boomer			= 2,
	L4D2ZombieClass_Hunter			= 3,
	L4D2ZombieClass_Spitter			= 4,
	L4D2ZombieClass_Jockey			= 5,
	L4D2ZombieClass_Charger			= 6,
	L4D2ZombieClass_Witch			= 7,
	L4D2ZombieClass_Tank			= 8,
	L4D2ZombieClass_NotInfected		= 9
}

enum L4D2UseAction
{
	L4D2UseAction_None				= 0,	// No use action active
	L4D2UseAction_Healing			= 1,	// Includes healing yourself or a teammate
	L4D2UseAction_AmmoPack			= 2,	// When deploying the ammo pack that was never added into the game
	L4D2UseAction_Defibing			= 4,	// When defib'ing a dead body
	L4D2UseAction_GettingDefibed	= 5,	// When comming back to life from a dead body
	L4D2UseAction_DeployIncendiary	= 6,	// When deploying Incendiary ammo
	L4D2UseAction_DeployExplosive	= 7,	// When deploying Explosive ammo
	L4D2UseAction_PouringGas		= 8,	// Pouring gas into a generator
	L4D2UseAction_Cola				= 9,	// For Dead Center map 2 cola event, when handing over the cola to Whitaker
	L4D2UseAction_Button			= 10,	// Such as buttons, timed buttons, generators, etc
	L4D2UseAction_UsePointScript	= 11	// When using a "point_script_use_target" entity
	/* List is not fully done, these are just the ones I have found so far */
}

enum L4DResourceType
{
	L4DResource_Ping,
	L4DResource_Score,
	L4DResource_TankTickets,
	L4DResource_Deaths,
	L4DResource_MaxHealth,
	L4DResource_WantsToPlay,
	L4DResource_TankTickets2
}

stock const char L4D1ZombieClassname[6][32] =
{
	"smoker",
	"boomer",
	"hunter",
	"witch",
	"tank",
	"error_bad_L4D1ZombieClassType"
};

stock const char L4D2ZombieClassname[9][32] =
{
	"smoker",
	"boomer",
	"hunter",
	"spitter",
	"jockey",
	"charger",
	"witch",
	"tank",
	"error_bad_L4D2ZombieClassType"
};

static const char L4DResourceName[L4DResourceType][] =
{
	"m_iPing",
	"m_iScore",
	"m_iTankTickets",
	"m_iDeaths",
	"m_maxHealth",
	"m_wantsToPlay",
	"m_tankTickets"
};

/**
 * Returns the clients team using L4DTeam
 *
 * @param client		Player's index
 * @return				Current L4DTeam of player
 * @error				Invalid client index
 */
stock L4DTeam L4D_GetClientTeam(int client)
{
	int team = GetClientTeam(client);
	return view_as<L4DTeam>(team);
}

/**
 * Changes the client's team using L4DTeam
 *
 * @param client		Player's index
 * @return				New L4DTeam of player
 * @error				Invalid client index
 */
stock void L4D_ChangeClientTeam(int client, L4DTeam team)
{
	ChangeClientTeam(client, view_as<int>(team));
}

/**
 * Returns zombie player L4D1 zombie class
 *
 * @param client		Player's index
 * @return				Current L4D1ZombieClassType of player
 * @error				Invalid client index
 */
// L4D1 only
stock L4D1ZombieClassType L4D1_GetPlayerZombieClass(int client)
{
	return view_as<L4D1ZombieClassType>(GetEntProp(client, Prop_Send, "m_zombieClass"));
}

/**
 * Set zombie player L4D1 zombie class
 *
 * @param client		Player's index
 * @param class			L4D1ZombieClassType class symbol
 * @noreturn
 * @error				Invalid client index
 */
// L4D1 only
stock void L4D1_SetPlayerZombieClass(int client, L4D1ZombieClassType class)
{
	SetEntProp(client, Prop_Send, "m_zombieClass", class);
}

/**
 * Returns zombie player L4D2 zombie class
 *
 * @param client		Player's index
 * @return				Current L4D2ZombieClassType of player
 * @error				Invalid client index
 */
// L4D2 only
stock any L4D2_GetPlayerZombieClass(int client)
{
	return view_as<L4D2ZombieClassType>(GetEntProp(client, Prop_Send, "m_zombieClass"));
}

/**
 * Set zombie player L4D2 zombie class
 *
 * @param client		Player's index
 * @param class			L4D2ZombieClassType class symbol
 * @noreturn
 * @error				Invalid client index
 */
// L4D2 only
stock void L4D2_SetPlayerZombieClass(int client, L4D2ZombieClassType class)
{
	SetEntProp(client, Prop_Send, "m_zombieClass", class);
}

/**
 * Returns zombie player L4D1 zombie classname from the L4D1ZombieClassType value
 *
 * @param type			L4D1ZombieClassType
 * @return				Classname string
 * @error				Invalid type index
 */
// L4D1 only
stock char[] L4D1_GetZombieClassname(L4D1ZombieClassType type)
{
	return L4D1ZombieClassname[type];
}

/**
 * Returns zombie player L4D2 zombie classname from the L4D2ZombieClassType value
 *
 * @param type			L4D1ZombieClassType
 * @return				Classname string
 * @error				Invalid type index
 */
// L4D2 only
stock char[] L4D2_GetZombieClassname(L4D2ZombieClassType type)
{
	return L4D2ZombieClassname[type];
}

/**
 * Returns ghost state of zombie player
 *
 * @param client		Player index
 * @return				True if player is ghost, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerGhost(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_isGhost", 1));
}

/**
 * Sets ghost state of zombie player
 *
 * @param client		Player index
 * @param isGhost		Sets ghost status
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerGhostState(int client, bool isGhost)
{
	SetEntProp(client, Prop_Send, "m_isGhost", isGhost, 1);
}

/**
 * Returns ghost spawn state of zombie player
 *
 * @param client		Player index
 * @return				Player's spawn state bits
 * @error				Invalid client index
 */
stock int L4D_GetPlayerGhostSpawnState(int client)
{
	return GetEntProp(client, Prop_Send, "m_ghostSpawnState");
}

/**
 * Set zombie player's ghost spawn state bits
 *
 * Note: The game updates spawn state bits every frame
 *
 * @param client		Player index
 * @param bits			Ghost spawn state bits
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerGhostSpawnState(int client, int bits)
{
	SetEntProp(client, Prop_Send, "m_ghostSpawnState", bits);
}

/**
 * Returns whether zombie player is culling
 *
 * @param client		Player index
 * @return				True if player is culling, false otherwise
 */
stock bool L4D_IsPlayerCulling(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_isCulling", 1));
}

/**
 * Set culling state of zombie player
 *
 * @param client		Player index
 * @param isCulling		Whether player is culling
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerCullingState(int client, bool isCulling)
{
	SetEntProp(client, Prop_Send, "m_isCulling", isCulling, 1);
}

/**
 * Returns whether player is incapacitated
 *
 * Note: A tank player will return true when in dying animation
 *
 * @param client		Player index
 * @return				True if incapacitated, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerIncapacitated(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_isIncapacitated", 1));
}

/**
 * Set player's incapacitated state
 *
 * @param client			Player index
 * @param isIncapacitated	Whether the player is incapacitated
 * @noreturn
 * @error					Invalid client index
 */
stock void L4D_SetPlayerIncapacitatedState(int client, bool isIncapacitated)
{
	SetEntProp(client, Prop_Send, "m_isIncapacitated", isIncapacitated, 1);
}

/**
 * Returns survivor player shove penalty
 *
 * @param client		Player index
 * @return				Current shove penalty of player
 */
stock int L4D_GetPlayerShovePenalty(int client)
{
	return GetEntProp(client, Prop_Send, "m_iShovePenalty");
}

/**
 * Set survivor player shove penalty
 *
 * @param client		Player index
 * @param shovePenalty	Shove penalty
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerShovePenalty(int client, int shovePenalty)
{
	SetEntProp(client, Prop_Send, "m_iShovePenalty", shovePenalty);
}

/**
 * Returns tank player's frustration
 *
 * @param client		Player index
 * @return				How frustrated tank player is
 * @error				Invalid client index
 */
stock int L4D_GetTankFrustration(int client)
{
	return GetEntProp(client, Prop_Send, "m_frustration");
}

/**
 * Set tank player's frustration
 *
 * @param client		Player index
 * @param frustration	How frustrated tank player is
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetTankFrustration(int client, int frustration)
{
	SetEntProp(client, Prop_Send, "m_frustration", frustration);
}

/**
 * Returns whether survivor player is idle
 *
 * @param				Player index
 * @return				True if idle, false otherwise
 */
stock bool L4D_IsPlayerIdle(int client)
{
	return L4D_GetBotOfIdlePlayer(client) > -1;
}

/**
 * Returns survivor bot of idle survivor player
 *
 * @param client		Player index
 * @return				Player index of the bot, -1 if not found
 */
stock int L4D_GetBotOfIdlePlayer(int client)
{
	int idleClient;
	int offset;
	char netclass[64];

	for (int bot = 1; bot <= MaxClients; bot++)
	{
		if (!IsClientInGame(bot) ||
			!IsFakeClient(bot) ||
			view_as<L4DTeam>(GetClientTeam(bot)) != L4DTeam_Survivor ||
			!IsPlayerAlive(bot) ||
			GetClientHealth(bot) < 1)
		{
			continue;
		}

		GetEntityNetClass(bot, netclass, sizeof(netclass));
		offset = FindSendPropInfo(netclass, "m_humanSpectatorUserID");

		if (offset < 1)
		{
			continue;
		}

		idleClient = GetClientOfUserId(GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID"));

		if (idleClient == client)
		{
			return bot;
		}
	}

	return -1;
}

/**
 * Returns idle survivor player of survivor bot
 *
 * @param bot			Player index of bot
 * @return				Player index of idle client, -1 if not found
 * @error				Invalid client index
 */
stock int L4D_GetIdlePlayerOfBot(int bot)
{
	char netclass[64];
	GetEntityNetClass(bot, netclass, sizeof(netclass));
	int offset = FindSendPropInfo(netclass, "m_humanSpectatorUserID");

	if (offset < 1)
	{
		return -1;
	}

	return GetClientOfUserId(GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID"));
}

/**
 * Returns resource entity
 *
 * @return				Entity index of resource entity, -1 if not found
 */
stock int L4D_GetResourceEntity()
{
	return FindEntityByClassname(-1, "terror_player_manager");
}

/**
 * Retrieves client data from the resource entity
 *
 * @param client		Player's index
 * @param type			ResourceType constant
 * @return				Value or -1 on failure
 * @error				Invalid client index, client not in game or failed to find resource entity
 */
stock int L4D_GetPlayerResourceData(int client, L4DResourceType type)
{
	if (!IsClientConnected(client))
	{
		return -1;
	}

	int offset = FindSendPropInfo("CTerrorPlayerResource", L4DResourceName[type]);

	if (offset < 1)
	{
		return -1;
	}

	int entity = L4D_GetResourceEntity();

	if (entity == -1)
	{
		return -1;
	}

	return GetEntData(entity, offset + (client * 4));
}

/**
 * Sets client data in the resource entity
 *
 * Note: The game overwrites these values every frame, so changing them will have very little effect
 *
 * @param client		Player's index
 * @param type			ResourceType constant
 * @param value			Value to set
 * @return				Value or -1 on failure
 * @error				Invalid client index, client not in game or failed to find resource entity
 */
stock bool L4D_SetPlayerResourceData(int client, L4DResourceType type, any value)
{
	if (!IsClientConnected(client))
	{
		return false;
	}

	int offset = FindSendPropInfo("CTerrorPlayerResource", L4DResourceName[type]);

	if (offset < 1)
	{
		return false;
	}

	int entity = L4D_GetResourceEntity();

	if (entity == -1)
	{
		return false;
	}

	SetEntData(entity, offset + (client * 4), value);

	return true;
}

/**
 * Removes the weapon from a client's weapon slot
 *
 * @param client		Player's index
 * @param slot			Slot index
 * @noreturn
 * @error				Invalid client or lack of mod support
 */
stock void L4D_RemoveWeaponSlot(int client, L4DWeaponSlot slot)
{
	int weaponIndex;
	while ((weaponIndex = GetPlayerWeaponSlot(client, view_as<int>(slot))) != -1)
	{
		RemovePlayerItem(client, weaponIndex);
		RemoveEdict(weaponIndex);
	}
}

/**
 * Removes all weapons from a client
 *
 * @param client		Player's index
 * @noreturn
 * @error				Invalid client or lack of mod support
 */
stock void L4D_RemoveAllWeapons(int client)
{
	for (int i = 0; i <= 4; i++)
	{
		L4D_RemoveWeaponSlot(client, view_as<L4DWeaponSlot>(i));
	}
}

/**
 * Returns whether the finale is active
 *
 * Note: Finales can also be on other maps than just the finale map. A perfect
 * example of this is the Swamp Fever map 1 crescendo event. This event is
 * defined as a finale by Valve for some reason
 *
 * @return				True if finale is active, false otherwise
 */
stock bool L4D_IsFinaleActive()
{
	int entity = L4D_GetResourceEntity();

	if (entity == -1)
	{
		return false;
	}

	return view_as<bool>(GetEntProp(entity, Prop_Send, "m_isFinale", 1));
}

/**
 * Returns whether any survivor have left the safe area
 *
 * @return				True if any survivor have left safe area, false otherwise
 */
stock bool L4D_HasAnySurvivorLeftSafeAreaStock()
{
	int entity = L4D_GetResourceEntity();

	if (entity == -1)
	{
		return false;
	}

	return view_as<bool>(GetEntProp(entity, Prop_Send, "m_hasAnySurvivorLeftSafeArea", 1));
}

/**
 * Returns pending tank player
 *
 * @return				Player index of pending tank player, -1 if not found
 */
stock int L4D_GetPendingTankPlayer()
{
	int entity = L4D_GetResourceEntity();

	if (entity == -1)
	{
		return -1;
	}

	return GetEntProp(entity, Prop_Send, "m_pendingTankPlayerIndex");
}

/**
 * Set entity glow. This is consider safer and more robust over setting each glow property on their own because glow offset will be check first
 *
 * @param entity		Entity index
 * @parma type			Glow type
 * @param range			Glow max range, 0 for unlimited
 * @param minRange		Glow min range
 * @param colorOverride	Glow color, RGB
 * @param flashing		Whether the glow will be flashing
 * @return				True if glow was set, false if entity does not support glow
 */
// L4D2 only
stock bool L4D2_SetEntityGlow(int entity, L4D2GlowType type, int range, int minRange, int colorOverride[3], bool flashing)
{
	if (!IsValidEntity(entity))
	{
		return false;
	}

	char netclass[64];
	GetEntityNetClass(entity, netclass, sizeof(netclass));

	int offset = FindSendPropInfo(netclass, "m_iGlowType");

	if (offset < 1)
	{
		return false;
	}

	L4D2_SetEntityGlow_Type(entity, type);
	L4D2_SetEntityGlow_Range(entity, range);
	L4D2_SetEntityGlow_MinRange(entity, minRange);
	L4D2_SetEntityGlow_Color(entity, colorOverride);
	L4D2_SetEntityGlow_Flashing(entity, flashing);
	return true;
}

/**
 * Set entity glow type
 *
 * @param entity		Entity index
 * @parma type			Glow type
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_SetEntityGlow_Type(int entity, L4D2GlowType type)
{
	SetEntProp(entity, Prop_Send, "m_iGlowType", type);
}

/**
 * Set entity glow range
 *
 * @param entity		Entity index
 * @parma range			Glow range
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_SetEntityGlow_Range(int entity, int range)
{
	SetEntProp(entity, Prop_Send, "m_nGlowRange", range);
}

/**
 * Set entity glow min range
 *
 * @param entity		Entity index
 * @parma minRange		Glow min range
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_SetEntityGlow_MinRange(int entity, int minRange)
{
	SetEntProp(entity, Prop_Send, "m_nGlowRangeMin", minRange);
}

/**
 * Set entity glow color
 *
 * @param entity		Entity index
 * @parma colorOverride	Glow color, RGB
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_SetEntityGlow_Color(int entity, int colorOverride[3])
{
	SetEntProp(entity, Prop_Send, "m_glowColorOverride", colorOverride[0] + (colorOverride[1] * 256) + (colorOverride[2] * 65536));
}

/**
 * Set entity glow flashing state
 *
 * @param entity		Entity index
 * @parma flashing		Whether glow will be flashing
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_SetEntityGlow_Flashing(int entity, bool flashing)
{
	SetEntProp(entity, Prop_Send, "m_bFlashing", flashing);
}

/**
 * Returns entity glow type
 *
 * @param entity		Entity index
 * @return				L4D2 glow type
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock L4D2GlowType L4D2_GetEntityGlow_Type(int entity)
{
	return view_as<L4D2GlowType>(GetEntProp(entity, Prop_Send, "m_iGlowType"));
}

/**
 * Returns entity glow range
 *
 * @param entity		Entity index
 * @return				Glow range
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock int L4D2_GetEntityGlow_Range(int entity)
{
	return GetEntProp(entity, Prop_Send, "m_nGlowRange");
}

/**
 * Returns entity glow min range
 *
 * @param entity		Entity index
 * @return				Glow min range
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock int L4D2_GetEntityGlow_MinRange(int entity)
{
	return GetEntProp(entity, Prop_Send, "m_nGlowRangeMin");
}

/**
 * Returns entity glow flashing state
 *
 * @param entity		Entity index
 * @return				Glow flashing state
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock bool L4D2_GetEntityGlow_Flashing(int entity)
{
	return view_as<bool>(GetEntProp(entity, Prop_Send, "m_bFlashing"));
}

/**
 * Removes entity glow
 *
 * @param entity		Entity index
 * @return				True if glow was removed, false if entity does not
 *						support glow
 */
// L4D2 only
stock bool L4D2_RemoveEntityGlow(int entity)
{
	return view_as<bool>(L4D2_SetEntityGlow(entity, L4D2Glow_None, 0, 0, { 0, 0, 0 }, false));
}

/**
 * Removes entity glow override color
 *
 * Note: This only removes the override color and reset it to the default glow
 * color
 *
 * @param entity		Entity index
 * @noreturn
 * @error				Invalid entity index or entity does not support glow
 */
// L4D2 only
stock void L4D2_RemoveEntityGlow_Color(int entity)
{
	L4D2_SetEntityGlow_Color(entity, { 0, 0, 0 });
}

/**
 * Whether survivor glow for player is enabled
 *
 * @param client		Client index
 * @return				True if survivor glow is enabled, false otherwise
 * @error				Invalid client index
 */
// L4D2 only
stock bool L4D2_IsPlayerSurvivorGlowEnable(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_bSurvivorGlowEnabled"));
}

/**
 * Set survivor glow state for player
 *
 * @param client		Client index
 * @param enabled		Whether survivor glow is enabled
 * @noreturn
 * @error				Invalid client index
 */
// L4D2 only
stock void L4D2_SetPlayerSurvivorGlowState(int client, bool enabled)
{
	SetEntProp(client, Prop_Send, "m_bSurvivorGlowEnabled", enabled);
}

/**
 * Return player current revive count
 *
 * @param client		Client index
 * @return				Survivor's current revive count
 * @error				Invalid client index
 */
stock int L4D_GetPlayerReviveCount(int client)
{
	return GetEntProp(client, Prop_Send, "m_currentReviveCount");
}

/**
 * Set player revive count
 *
 * @param client		Client index
 * @param count			Revive count
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerReviveCount(int client, int count)
{
	SetEntProp(client, Prop_Send, "m_currentReviveCount", count);
}

/**
 * Return player intensity
 *
 * Note: Its percentage. 0.0 - Player is calm, 1.0 - Player is stressed
 *
 * @param client		Client index
 * @return				Intensity
 * @error				Invalid client index
 */
stock float L4D_GetPlayerIntensity(int client)
{
	/* This format is used to keep consistency with the Director which also
	 * uses 0.0 for calm and 1.0 for stressed */
	return float(GetEntProp(client, Prop_Send, "m_clientIntensity")) / 100.0;
}

/**
 * Returns average survivor intensity
 *
 * Note: Its percentage. 0.0 - All survivors is calm, 1.0 - All survivors is stressed
 *
 * @return				Average intensity level for survivors
 */
stock float L4D_GetAvgSurvivorIntensity()
{
	int intensityTotal = 0;
	int intensityMaxTotal = 0;

	for (int client = 1; client <= MaxClients; client++)
	{
		if (!IsClientInGame(client) ||
			view_as<L4DTeam>(GetClientTeam(client)) != L4DTeam_Survivor ||
			!IsPlayerAlive(client) ||
			GetClientHealth(client) < 1)
		{
			continue;
		}

		intensityMaxTotal += 100;
		intensityTotal += GetEntProp(client, Prop_Send, "m_clientIntensity");
	}

	/* This format is used to keep consistency with the Director which also uses 0.0 for calm and 1.0 for stressed */
	return float(intensityTotal) / float(intensityMaxTotal);
}

/**
 * Set player intensity
 *
 * Note: Its percentage. 0.0 - Player is calm, 1.0 - Player is stressed
 *
 * @param client		Client index
 * @param intensity		Intensity
 * @noreturn
 * @error				Invalid client index
 */
/*
// MOVED TO NATIVES:
stock void L4D_SetPlayerIntensity(int client, float intensity)
{
	SetEntProp(client, Prop_Send, "m_clientIntensity", RoundToNearest(intensity * 100.0));
}
*/

/**
 * Returns whether player is calm
 *
 * Note: Player is calm means that the player have not taken damage or
 * fired their weapon for a while. Survivor bots always return false
 *
 * @param client		Client index
 * @return				True if player is calm, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerCalm(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_isCalm"));
}

/**
 * Set player is calm state
 *
 * Note: Player is calm means that the player have not taken damage or fired their weapon for a while
 *
 * @param client		Client index
 * @param isCalm		Whether player is calm
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerCalmState(int client, bool isCalm)
{
	SetEntProp(client, Prop_Send, "m_isCalm", isCalm);
}

/**
 * Returns whether player has visible threats
 *
 * Note: This function should work for all players. Survivors looking upon
 * specials, witch or tank will be marked as has visible threats. However
 * looking at commons will not be seen as has visible threats. The common has
 * to be awaken from its slumber before beings seen as a threat
 *
 * @parma client		Client index
 * @return				True if player has visible threats, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_HasVisibleThreats(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_hasVisibleThreats"));
}

/**
 * Returns whether player is on third strike
 *
 * @param client		Client index
 * @return				True if on third strike, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerOnThirdStrike(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_bIsOnThirdStrike"));
}

/**
 * Set player third strike state
 *
 * @param client		Client index
 * @param onThirdStrike Whether survivor is on third strike
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerThirdStrikeState(int client, bool onThirdStrike)
{
	SetEntProp(client, Prop_Send, "m_bIsOnThirdStrike", onThirdStrike);
}

/**
 * Returns whether player is going to die
 *
 * Note: This is not the same as is player on third strike. While on third
 * strike defines whether player should die next time they get incapacitated,
 * this defines whether the survivor should limp when they hit 1hp and make
 * the character vocalize their "I dont think I'm gonna make it" lines
 *
 * @param client		Client index
 * @return				True if player is going to die, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerGoingToDie(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_isGoingToDie"));
}

/**
 * Set player is going to die state
 *
 * @param client		Client index
 * @param isGoingToDie	Whether player is going to die
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerIsGoingToDie(int client, bool isGoingToDie)
{
	SetEntProp(client, Prop_Send, "m_isGoingToDie", isGoingToDie);
}

/**
 * Returns whether weapon is upgrade compatible
 *
 * @param weapon		Weapon entity index
 * @return				True if compatible with upgrades, false otherwise
 * @error				Invalid entity index
 */
stock bool L4D2_IsWeaponUpgradeCompatible(int weapon)
{
	if( HasEntProp(weapon, Prop_Send, "m_upgradeBitVec") )
		return true;

	return false;
}

/**
 * Returns upgraded ammo count for weapon
 *
 * @param weapon		Weapon entity index
 * @return				Upgraded ammo count
 * @error				Invalid entity index
 */
// L4D2 only
stock int L4D2_GetWeaponUpgradeAmmoCount(int weapon)
{
	if( HasEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded") )
		return GetEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded");

	return 0;
}

/**
 * Set upgraded ammo count in weapon
 *
 * @param weapon		Weapon entity index
 * @param count			Upgraded ammo count
 * @noreturn
 * @error				Invalid entity index
 */
// L4D2 only
stock void L4D2_SetWeaponUpgradeAmmoCount(int weapon, int count)
{
	if( HasEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded") )
		SetEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded", count);
}

/**
 * Returns weapon upgrades of weapon
 *
 * @param weapon		Weapon entity index
 * @return				Weapon upgrade bits
 * @error				Invalid entity index
 */
// L4D2 only
stock int L4D2_GetWeaponUpgrades(int weapon)
{
	if( HasEntProp(weapon, Prop_Send, "m_upgradeBitVec") )
		return GetEntProp(weapon, Prop_Send, "m_upgradeBitVec");

	return 0;
}

/**
 * Set weapon upgrades for weapon
 *
 * @param weapon		Weapon entity index
 * @param upgrades		Weapon upgrade bits
 * @noreturn
 * @error				Invalid entity index
 */
// L4D2 only
stock void L4D2_SetWeaponUpgrades(int weapon, int upgrades)
{
	if( HasEntProp(weapon, Prop_Send, "m_upgradeBitVec") )
	{
		SetEntProp(weapon, Prop_Send, "m_upgradeBitVec", upgrades);
	}
}

/**
 * Returns infected attacker of survivor victim
 *
 * Note: Infected attacker means the infected player that is currently
 * pinning down the survivor. Such as hunter, smoker, charger and jockey
 *
 * You can use the native L4D2_GetSpecialInfectedDominatingMe() which is likely faster, instead of this stock
 *
 * @param client		Survivor client index
 * @return				Infected attacker index, -1 if not found
 * @error				Invalid client index
 */
// L4D2 only
stock int L4D2_GetInfectedAttacker(int client)
{
	int attacker;

	/* Charger */
	attacker = GetEntPropEnt(client, Prop_Send, "m_pummelAttacker");
	if (attacker > 0)
	{
		return attacker;
	}

	attacker = GetEntPropEnt(client, Prop_Send, "m_carryAttacker");
	if (attacker > 0)
	{
		return attacker;
	}

	/* Hunter */
	attacker = GetEntPropEnt(client, Prop_Send, "m_pounceAttacker");
	if (attacker > 0)
	{
		return attacker;
	}

	/* Smoker */
	attacker = GetEntPropEnt(client, Prop_Send, "m_tongueOwner");
	if (attacker > 0)
	{
		return attacker;
	}

	/* Jockey */
	attacker = GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker");
	if (attacker > 0)
	{
		return attacker;
	}

	return -1;
}

/**
 * Returns survivor victim of infected attacker
 *
 * Note: Survivor victim means the survivor player that is currently pinned
 * down by an attacker. Such as hunter, smoker, charger and jockey
 *
 * @param client		Infected client index
 * @return				Survivor victim index, -1 if not found
 * @error				Invalid client index
 */
// L4D2 only
stock int L4D2_GetSurvivorVictim(int client)
{
	int victim;

	/* Charger */
	victim = GetEntPropEnt(client, Prop_Send, "m_pummelVictim");
	if (victim > 0)
	{
		return victim;
	}

	victim = GetEntPropEnt(client, Prop_Send, "m_carryVictim");
	if (victim > 0)
	{
		return victim;
	}

	/* Hunter */
	victim = GetEntPropEnt(client, Prop_Send, "m_pounceVictim");
	if (victim > 0)
	{
		return victim;
	}

	/* Smoker */
	victim = GetEntPropEnt(client, Prop_Send, "m_tongueVictim");
	if (victim > 0)
	{
		return victim;
	}

	/* Jockey */
	victim = GetEntPropEnt(client, Prop_Send, "m_jockeyVictim");
	if (victim > 0)
	{
		return victim;
	}

	return -1;
}

/**
 * Returns whether survivor client was present at survival start
 *
 * @param client		Client index
 * @return				True if survivor was present at survival start, false otherwise
 * @error				Invalid client index
 */
// L4D2 only
stock bool L4D2_WasPresentAtSurvivalStart(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_bWasPresentAtSurvivalStart"));
}

/**
 * Sets whether player was present at survival start
 *
 * @param client		Client index
 * @param wasPresent	Whether survivor was present at survival start
 * @noreturn
 * @error				Invalid client index
 */
// L4D2 only
stock void L4D2_SetPresentAtSurvivalStart(int client, bool wasPresent)
{
	SetEntProp(client, Prop_Send, "m_bWasPresentAtSurvivalStart", wasPresent);
}

/**
 * Returns whether player is using a mounted weapon
 *
 * @param client		Client index
 * @return				True if using a mounted weapon, false otherwise
 * @error				Invalid client index
 */
stock bool L4D_IsPlayerUsingMountedWeapon(int client)
{
	return view_as<bool>(GetEntProp(client, Prop_Send, "m_usingMountedWeapon"));
}

/**
 * Returns player temporarily health
 *
 * Note: This will not work with mutations or campaigns that alters the decay
 * rate through vscript'ing. If you want to be sure that it works no matter
 * the mutation, you will have to detour the OnGetScriptValueFloat function
 * Doing so you are able to capture the altered decay rate and calculate the
 * temp health the same way as this function does
 *
 * @param client		Client index
 * @return				Player's temporarily health, -1 if unable to get
 * @error				Invalid client index or unable to find pain_pills_decay_rate cvar
 */
stock int L4D_GetPlayerTempHealth(int client)
{
	static ConVar painPillsDecayCvar;
	if (painPillsDecayCvar == null)
	{
		painPillsDecayCvar = FindConVar("pain_pills_decay_rate");
		if (painPillsDecayCvar == null)
		{
			return -1;
		}
	}

	int tempHealth = RoundToCeil(GetEntPropFloat(client, Prop_Send, "m_healthBuffer") - ((GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime")) * painPillsDecayCvar.FloatValue)) - 1;
	return tempHealth < 0 ? 0 : tempHealth;
}

/**
 * Set players temporarily health
 *
 * @param client		Client index
 * @param tempHealth	Temporarily health
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerTempHealth(int client, int tempHealth)
{
	SetEntPropFloat(client, Prop_Send, "m_healthBuffer", float(tempHealth));
	SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime());
}

/**
 * Set players temporarily health. Allows for setting above 200 HP
 *
 * @param client		Client index
 * @param tempHealth	Temporarily health
 * @noreturn
 * @error				Invalid client index
 */
stock void L4D_SetPlayerTempHealthFloat(int client, float tempHealth)
{
	static ConVar painPillsDecayCvar;
	if (painPillsDecayCvar == null)
	{
		painPillsDecayCvar = FindConVar("pain_pills_decay_rate");
		if (painPillsDecayCvar == null)
		{
			return;
		}
	}

	SetEntPropFloat(client, Prop_Send, "m_healthBuffer", tempHealth);
	SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime() + ((tempHealth - 200) / painPillsDecayCvar.FloatValue + 1 / painPillsDecayCvar.FloatValue));
}

/**
 * Returns player use action
 *
 * @param client		Client index
 * @return				Use action
 * @error				Invalid client index
 */
// L4D2 only
stock L4D2UseAction L4D2_GetPlayerUseAction(int client)
{
	return view_as<L4D2UseAction>(GetEntProp(client, Prop_Send, "m_iCurrentUseAction"));
}

/**
 * Returns player use action target
 *
 * @param client		Client index
 * @return				Entity index
 * @error				Invalid client index
 */
// L4D2 only
stock int L4D2_GetPlayerUseActionTarget(int client)
{
	return GetEntPropEnt(client, Prop_Send, "m_useActionTarget");
}

/**
 * Returns player use action owner
 *
 * @param client		Client index
 * @return				Entity index
 * @error				Invalid client index
 */
// L4D2 only
stock int L4D2_GetPlayerUseActionOwner(int client)
{
	return GetEntPropEnt(client, Prop_Send, "m_useActionOwner");
}

/**
 * Creates an instructor hint
 *
 * Note: Both infected and survivor players will see hint. No more than one at
 * a time can be shown. The "newest" hint will override the old no matter the
 * timeout and range. This instructor hint will not be shown if the given
 * player is dead
 *
 * @param name			Instructor hint name
 * @param target		Entity index of target
 * @param caption		Caption of hint
 * @param color			Color of the caption. RGB format
 * @param iconOnScreen	Icon when hint is on screen
 * @param iconOffScreen	Icon when hint is off screen
 * @param binding		Key binding to show
 * @param iconOffset	Height offset for icon from target entity's origin
 * @param range			Display range of hint. 0 for unlimited range
 * @param timeout		Timeout out for hint. 0 will persist until stopped with L4D2_EndInstructorHint
 * @param allowNoDrawTarget	Whether hint will work with targets that have nodraw set
 * @param noOffScreen	Whether when hint is off screen it will show an arrow pointing to target
 * @param forceCaption	Whether the hint and icon will show even when occluded a wall
 * @param flags			Instructor hint bit flags. See L4D2_IHFLAG_* defines
 * @return				True if send, false otherwise
 */
// L4D2 only
stock bool L4D2_CreateInstructorHint(const char[] name,
									int target = 0,
									const char[] caption,
									int color[3] = {255, 255, 255},
									const char[] iconOnScreen = "icon_tip",
									const char[] iconOffScreen = "icon_tip",
									const char[] binding = "",
									float iconOffset = 0.0,
									float range = 0.0,
									int timeout = 0,
									bool allowNoDrawTarget = true,
									bool noOffScreen = false,
									bool forceCaption = false,
									int flags = L4D2_IHFLAG_STATIC)
{
	Event event = CreateEvent("instructor_server_hint_create", true);
	if (event == null)
	{
		return false;
	}

	char finalizedColor[16];
	Format(finalizedColor, 16, "%d,%d,%d", color[0], color[1], color[2]);

	event.SetString("hint_name", name);
	event.SetInt("hint_target", target);
	event.SetString("hint_caption", caption);
	event.SetString("hint_color", finalizedColor);
	event.SetString("hint_icon_onscreen", iconOnScreen);
	event.SetString("hint_icon_offscreen", iconOffScreen);
	event.SetString("hint_binding", binding);
	event.SetFloat("hint_icon_offset", iconOffset);
	event.SetFloat("hint_range", range);
	event.SetInt("hint_timeout", timeout);
	event.SetBool("hint_allow_nodraw_target", allowNoDrawTarget);
	event.SetBool("hint_nooffscreen", noOffScreen);
	event.SetBool("hint_forcecaption", forceCaption);
	event.SetInt("hint_flags", flags);
	event.Fire();
	return true;
}

/**
 * Stops all instructor hints with name
 *
 * @param name			Name of instructor hint to stop
 * @return				True if send, false otherwise
 */
// L4D2 only
stock bool L4D2_StopInstructorHint(const char[] name)
{
	Event event = CreateEvent("instructor_server_hint_stop", true);
	if (event == null)
	{
		return false;
	}

	event.SetString("hint_name", name);
	event.Fire();
	return true;
}

/**
 * Returns whether shotgun needs to be pumped
 *
 * @parma weapon		Weapon entity index
 * @return				True if shotgun needs to be pumped, false otherwise
 */
// L4D1 only
stock int L4D1_GetShotgunNeedPump(int weapon)
{
	return HasEntProp(weapon, Prop_Send, "m_needPump") && GetEntProp(weapon, Prop_Send, "m_needPump");
}

/**
 * Sets whether shotgun needs to be pumped
 *
 * @parma weapon		Weapon entity index
 * @parma needPump		Whether shotgun needs to be pumped
 * @noreturn
 */
// L4D1 only
stock void L4D1_SetShotgunNeedPump(int weapon, bool needPump)
{
	if( HasEntProp(weapon, Prop_Send, "m_needPump") )
	{
		SetEntProp(weapon, Prop_Send, "m_needPump", view_as<int>(needPump));
	}
}

/**
 * Sets custom ability cooldown of client
 *
 * Note: Used for the Infected class abilities
 *
 * @param client		Client index
 * @param time			How long before client can use their custom ability
 * @return				True if set, false if no ability found
 */
// L4D2 only
stock bool L4D2_SetCustomAbilityCooldown(int client, float time)
{
	int ability = GetEntPropEnt(client, Prop_Send, "m_customAbility");
	if (ability > 0 && IsValidEdict(ability))
	{
		SetEntPropFloat(ability, Prop_Send, "m_duration", time);
		SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + time);
		return true;
	}
	return false;
}





// ====================================================================================================
// WEAPON STOCKS: "l4d_weapon_stocks.inc" by "Mr. Zero"
// ====================================================================================================

/* Credits to ProdigySim for the weapon details, models and original script */

#if defined _l4d_weapon_stocks_included
 #endinput
#endif
#define _l4d_weapon_stocks_included

#include <adt_trie>
#include <sourcemod>
#include <sdktools>

enum L4D2WeaponId
{
	L4D2WeaponId_None,				// 0
	L4D2WeaponId_Pistol,			// 1
	L4D2WeaponId_Smg,				// 2
	L4D2WeaponId_Pumpshotgun,		// 3
	L4D2WeaponId_Autoshotgun,		// 4
	L4D2WeaponId_Rifle,				// 5
	L4D2WeaponId_HuntingRifle,		// 6
	L4D2WeaponId_SmgSilenced,		// 7
	L4D2WeaponId_ShotgunChrome,		// 8
	L4D2WeaponId_RifleDesert,		// 9
	L4D2WeaponId_SniperMilitary,	// 10
	L4D2WeaponId_ShotgunSpas,		// 11
	L4D2WeaponId_FirstAidKit,		// 12
	L4D2WeaponId_Molotov,			// 13
	L4D2WeaponId_PipeBomb,			// 14
	L4D2WeaponId_PainPills,			// 15
	L4D2WeaponId_Gascan,			// 16
	L4D2WeaponId_PropaneTank,		// 17
	L4D2WeaponId_OxygenTank,		// 18
	L4D2WeaponId_Melee,				// 19
	L4D2WeaponId_Chainsaw,			// 20
	L4D2WeaponId_GrenadeLauncher,	// 21
	L4D2WeaponId_AmmoPack,			// 22
	L4D2WeaponId_Adrenaline,		// 23
	L4D2WeaponId_Defibrillator,		// 24
	L4D2WeaponId_Vomitjar,			// 25
	L4D2WeaponId_RifleAK47,			// 26
	L4D2WeaponId_GnomeChompski,		// 27
	L4D2WeaponId_ColaBottles,		// 28
	L4D2WeaponId_FireworksBox,		// 29
	L4D2WeaponId_IncendiaryAmmo,	// 30
	L4D2WeaponId_FragAmmo,			// 31
	L4D2WeaponId_PistolMagnum,		// 32
	L4D2WeaponId_SmgMP5,			// 33
	L4D2WeaponId_RifleSG552,		// 34
	L4D2WeaponId_SniperAWP,			// 35
	L4D2WeaponId_SniperScout,		// 36
	L4D2WeaponId_RifleM60,			// 37
	L4D2WeaponId_TankClaw,			// 38
	L4D2WeaponId_HunterClaw,		// 39
	L4D2WeaponId_ChargerClaw,		// 40
	L4D2WeaponId_BoomerClaw,		// 41
	L4D2WeaponId_SmokerClaw,		// 42
	L4D2WeaponId_SpitterClaw,		// 43
	L4D2WeaponId_JockeyClaw,		// 44
	L4D2WeaponId_Machinegun,		// 45
	L4D2WeaponId_FatalVomit,		// 46
	L4D2WeaponId_ExplodingSplat,	// 47
	L4D2WeaponId_LungePounce,		// 48
	L4D2WeaponId_Lounge,			// 49
	L4D2WeaponId_FullPull,			// 50
	L4D2WeaponId_Choke,				// 51
	L4D2WeaponId_ThrowingRock,		// 52
	L4D2WeaponId_TurboPhysics,		// 53
	L4D2WeaponId_Ammo,				// 54
	L4D2WeaponId_UpgradeItem,		// 55
	L4D2WeaponId_MAX
};

static const char L4D2WeaponName[L4D2WeaponId][] =
{
	"weapon_none",					// 0
	"weapon_pistol",				// 1
	"weapon_smg",					// 2
	"weapon_pumpshotgun",			// 3
	"weapon_autoshotgun",			// 4
	"weapon_rifle",					// 5
	"weapon_hunting_rifle",			// 6
	"weapon_smg_silenced",			// 7
	"weapon_shotgun_chrome",		// 8
	"weapon_rifle_desert",			// 9
	"weapon_sniper_military",		// 10
	"weapon_shotgun_spas",			// 11
	"weapon_first_aid_kit",			// 12
	"weapon_molotov",				// 13
	"weapon_pipe_bomb",				// 14
	"weapon_pain_pills",			// 15
	"weapon_gascan",				// 16
	"weapon_propanetank",			// 17
	"weapon_oxygentank",			// 18
	"weapon_melee",					// 19
	"weapon_chainsaw",				// 20
	"weapon_grenade_launcher",		// 21
	"weapon_ammo_pack",				// 22
	"weapon_adrenaline",			// 23
	"weapon_defibrillator",			// 24
	"weapon_vomitjar",				// 25
	"weapon_rifle_ak47",			// 26
	"weapon_gnome",					// 27
	"weapon_cola_bottles",			// 28
	"weapon_fireworkcrate",			// 29
	"weapon_upgradepack_incendiary",	// 30
	"weapon_upgradepack_explosive",		// 31
	"weapon_pistol_magnum",			// 32
	"weapon_smg_mp5",				// 33
	"weapon_rifle_sg552",			// 34
	"weapon_sniper_awp",			// 35
	"weapon_sniper_scout",			// 36
	"weapon_rifle_m60",				// 37
	"weapon_tank_claw",				// 38
	"weapon_hunter_claw",			// 39
	"weapon_charger_claw",			// 40
	"weapon_boomer_claw",			// 41
	"weapon_smoker_claw",			// 42
	"weapon_spitter_claw",			// 43
	"weapon_jockey_claw",			// 44
	"weapon_machinegun",			// 45
	"vomit",						// 46
	"splat",						// 47
	"pounce",						// 48
	"lounge",						// 49
	"pull",							// 50
	"choke",						// 51
	"rock",							// 52
	"physics",						// 53
	"weapon_ammo",					// 54
	"upgrade_item",					// 55
	""
};

static const char L4D2WeaponWorldModel[L4D2WeaponId][] =
{
	"",
	"/w_models/weapons/w_pistol_b.mdl",
	"/w_models/weapons/w_smg_uzi.mdl",
	"/w_models/weapons/w_shotgun.mdl",
	"/w_models/weapons/w_autoshot_m4super.mdl",
	"/w_models/weapons/w_rifle_m16a2.mdl",
	"/w_models/weapons/w_sniper_mini14.mdl",
	"/w_models/weapons/w_smg_a.mdl",
	"/w_models/weapons/w_pumpshotgun_a.mdl",
	"/w_models/weapons/w_desert_rifle.mdl",			// "/w_models/weapons/w_rifle_b.mdl"
	"/w_models/weapons/w_sniper_military.mdl",
	"/w_models/weapons/w_shotgun_spas.mdl",
	"/w_models/weapons/w_eq_medkit.mdl",
	"/w_models/weapons/w_eq_molotov.mdl",
	"/w_models/weapons/w_eq_pipebomb.mdl",
	"/w_models/weapons/w_eq_painpills.mdl",
	"/props_junk/gascan001a.mdl",
	"/props_junk/propanecanister001.mdl",
	"/props_equipment/oxygentank01.mdl",
	"",		// "/weapons/w_knife_t.mdl",
			// "/weapons/melee/w_bat.mdl",
			// "/weapons/melee/w_chainsaw.mdl
			// "/weapons/melee/w_cricket_bat.mdl",
			// "/weapons/melee/w_crowbar.mdl",
			// "/weapons/melee/w_didgeridoo.mdl",
			// "/weapons/melee/w_electric_guitar.mdl",
			// "/weapons/melee/w_fireaxe.mdl",
			// "/weapons/melee/w_frying_pan.mdl",
			// "/weapons/melee/w_golfclub.mdl",
			// "/weapons/melee/w_katana.mdl",
			// "/weapons/melee/w_machete.mdl",
			// "/weapons/melee/w_riotshield.mdl",
			// "/weapons/melee/w_tonfa.mdl"
	"/weapons/melee/w_chainsaw.mdl",
	"/w_models/weapons/w_grenade_launcher.mdl",
	"",
	"/w_models/weapons/w_eq_adrenaline.mdl",
	"/w_models/weapons/w_eq_defibrillator.mdl",
	"/w_models/weapons/w_eq_bile_flask.mdl",
	"/w_models/weapons/w_rifle_ak47.mdl",
	"/props_junk/gnome.mdl",
	"/w_models/weapons/w_cola.mdl",
	"/props_junk/explosive_box001.mdl",
	"/w_models/weapons/w_eq_incendiary_ammopack.mdl",
	"/w_models/weapons/w_eq_explosive_ammopack.mdl",
	"/w_models/weapons/w_desert_eagle.mdl",
	"/w_models/weapons/w_smg_mp5.mdl",
	"/w_models/weapons/w_rifle_sg552.mdl",
	"/w_models/weapons/w_sniper_awp.mdl",
	"/w_models/weapons/w_sniper_scout.mdl",
	"/w_models/weapons/w_m60.mdl",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	"",
	""
};

StringMap g_hWeaponNameTrie;

/**
 * Returns whether weapon id is valid
 *
 * @param weaponId		Weapon id to check for validity
 * @return				True if weapon id is valid, false otherwise
 */
// L4D2 only
stock bool L4D2_IsValidWeaponId(L4D2WeaponId weaponId)
{
	return weaponId >= L4D2WeaponId_None && weaponId < L4D2WeaponId_MAX;
}

/**
 * Returns whether weapon name is a valid weapon
 *
 * @param weaponName	Weapon name to check for validity
 * @return				True if weapon name is valid, false otherwise
 */
// L4D2 only
stock bool L4D2_IsValidWeaponName(const char[] weaponName)
{
	return L4D2_GetWeaponIdByWeaponName(weaponName) != L4D2WeaponId_None;
}

/**
 * Checks to see if a given weapon id has a known WeaponModel in this file's model array
 *
 * Note: The melee weapon have multiple valid models. This function will
 * return false for melee weapon
 *
 * @param weaponId		Weapon id to check for a known weapon model for
 * @return				True if a valid weapon model exists for weapon id, false otherwise
 */
// L4D2 only
stock bool L4D2_HasValidWeaponWorldModel(L4D2WeaponId weaponId)
{
	return L4D2WeaponWorldModel[weaponId][0] != '\0';
}

/**
 * Returns weapon world model by weapon id
 *
 * @note Does not work with melee weapons
 *
 * @param weaponId		Weapon id
 * @param dest			Destination string buffer to copy to
 * @param destlen		Destination buffer length (includes null terminator)
 * @return				Number of cells written
 */
// L4D2 only
stock int L4D2_GetWeaponModelByWeaponId(L4D2WeaponId weaponId, char[] dest, int destlen)
{
	if (!L4D2_IsValidWeaponId(weaponId))
	{
		return 0;
	}

	return strcopy(dest, destlen, L4D2WeaponWorldModel[weaponId]);
}

/**
 * Returns weapon id by weapon world model
 *
 * @note Does not work with melee weapons
 *
 * @param model			Weapon world model
 * @return				Weapon Id
 */
// L4D2 only
stock L4D2WeaponId L4D2_GetWeaponIdByWeaponModel(const char[] model)
{
	for (int i = 0; i < sizeof(L4D2WeaponWorldModel); i++)
	{
		if (strcmp(model, L4D2WeaponWorldModel[i], false) == 0)
		{
			return view_as<L4D2WeaponId>(i);
		}
	}

	return L4D2WeaponId_None;
}

/**
 * Returns weapon id by weapon name
 *
 * @param weaponName	Weapon name to get id from
 * @return				The corresponding weapon id if found, else L4D2WeaponId_None
 */
// L4D2 only
stock L4D2WeaponId L4D2_GetWeaponIdByWeaponName(const char[] weaponName)
{
	L4D2_InitWeaponNameTrie();
	L4D2WeaponId weaponId;
	return g_hWeaponNameTrie.GetValue(weaponName, weaponId) ? weaponId : L4D2WeaponId_None;
}

/**
 * Returns weapon name by weapon id
 *
 * @param weaponName	Weapon id to get name from
 * @param dest			Destination string buffer to copy to
 * @param destlen		Destination buffer length (includes null terminator)
 * @return				Number of cells written
 */
// L4D2 only
stock int L4D2_GetWeaponNameByWeaponId(L4D2WeaponId weaponId, char[] dest, int destlen)
{
	if (!L4D2_IsValidWeaponId(weaponId))
	{
		return 0;
	}

	return strcopy(dest, destlen, L4D2WeaponName[weaponId]);
}

/**
 * Returns weapon id of entity
 *
 * @param weapon		Entity index of weapon
 * @return				Weapon id if found, L4D2WeaponId_None otherwise
 * @error				Invalid entity index
 */
// L4D2 only
stock L4D2WeaponId L4D2_GetWeaponId(int weapon)
{
	char classname[64];
	if (!GetEdictClassname(weapon, classname, sizeof(classname)))
	{
		return L4D2WeaponId_None;
	}

	if (strcmp(classname, "weapon_spawn") == 0)
	{
		return view_as<L4D2WeaponId>(GetEntProp(weapon, Prop_Send, "m_weaponID"));
	}

	int len = strlen(classname);
	if (len - 6 > 0 && strcmp(classname[len - 6], "_spawn") == 0)
	{
		classname[len - 6] = '\0';
	}

	return L4D2_GetWeaponIdByWeaponName(classname);
}

/**
 * Initialize the L4D2 weapon names trie. Not necessary to be executed, done by the functions that require the trie
 *
 * @noreturn
 */
// L4D2 only
stock void L4D2_InitWeaponNameTrie()
{
	if (g_hWeaponNameTrie != null)
	{
		return;
	}

	g_hWeaponNameTrie = new StringMap();
	for (int i = 0; i < view_as<int>(L4D2WeaponId_MAX); i++)
	{
		g_hWeaponNameTrie.SetValue(L4D2WeaponName[i], i);
	}
}