// vim: set ts=4 sw=4 tw=99 noet:
//
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team.
//
// This software is licensed under the GNU General Public License, version 3 or higher.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
//     https://alliedmods.net/amxmodx-license

//
// Counter-Strike Module
//

#include "CstrikeDatas.h"
#include "CstrikePlayer.h"
#include "CstrikeUtils.h"
#include "CstrikeHacks.h"
#include "CstrikeUserMessages.h"
#include "CstrikeItemsInfos.h"
#include <CDetour/detours.h>
#include <amtl/am-string.h>
#include <resdk/mod_regamedll_api.h>

bool NoKnivesMode = false;
StringHashMap<int> ModelsList;

// native cs_set_user_money(index, money, flash = 1);
static cell AMX_NATIVE_CALL cs_set_user_money(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iAccount);

	int index = params[1];
	int money = params[2];
	int flash = static_cast<int>(params[3] != 0);

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<int>(pPlayer, m_iAccount, money);

	MESSAGE_BEGIN(MSG_ONE, MessageIdMoney, nullptr, pPlayer);
		WRITE_LONG(money);
		WRITE_BYTE(flash);
	MESSAGE_END();

	return 1;
}

// native cs_get_user_money(index);
static cell AMX_NATIVE_CALL cs_get_user_money(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iAccount);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<int>(pPlayer, m_iAccount);
}

// native cs_get_user_deaths(index);
static cell AMX_NATIVE_CALL cs_get_user_deaths(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iDeaths);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<int>(pPlayer, m_iDeaths);
}

// native cs_set_user_deaths(index, newdeaths, bool:scoreboard = true);
static cell AMX_NATIVE_CALL cs_set_user_deaths(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iDeaths);
	GET_OFFSET("CBasePlayer", m_iTeam );

	int index  = params[1];
	int deaths = params[2];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<int>(pPlayer, m_iDeaths, deaths);

	bool updateScoreboard = true;

	if (*params / sizeof(cell) >= 3)
	{
		updateScoreboard = params[3] != 0;
	}

	if (updateScoreboard)
	{
		MESSAGE_BEGIN(MSG_ALL, MessageIdScoreInfo);
			WRITE_BYTE(index);
			WRITE_SHORT(static_cast<int>(pPlayer->v.frags));
			WRITE_SHORT(deaths);
			WRITE_SHORT(0);
			WRITE_SHORT(get_pdata<int>(pPlayer, m_iTeam));
		MESSAGE_END();
	}

	*static_cast<int *>(MF_PlayerPropAddr(index, Player_Deaths)) = deaths;

	return 1;
}

// native cs_get_hostage_id(index);
static cell AMX_NATIVE_CALL cs_get_hostage_id(AMX *amx, cell *params)
{
	GET_OFFSET("CHostage", m_iHostageIndex);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);

	return get_pdata<int>(pHostage, m_iHostageIndex);
}

// native cs_get_weapon_silen(index);
static cell AMX_NATIVE_CALL cs_get_weapon_silenced(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerItem"  , m_iId);
	GET_OFFSET("CBasePlayerWeapon", m_iWeaponState);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	switch (get_pdata<int>(pWeapon, m_iId))
	{
		case CSW_M4A1:
		{
			if (get_pdata<int>(pWeapon, m_iWeaponState) & WPNSTATE_M4A1_SILENCED)
			{
				return 1;
			}
		}
		case CSW_USP:
		{
			if (get_pdata<int>(pWeapon, m_iWeaponState) & WPNSTATE_USP_SILENCED)
			{
				return 1;
			}
		}
	}

	return 0;
}

// native cs_get_weapon_id(index);
static cell AMX_NATIVE_CALL cs_get_weapon_id(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerItem", m_iId);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	return get_pdata<int>(pWeapon, m_iId);
}

// native cs_set_weapon_silen(index, silence = 1, draw_animation = 1);
static cell AMX_NATIVE_CALL cs_set_weapon_silenced(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerItem"  , m_iId);
	GET_OFFSET("CBasePlayerWeapon", m_iWeaponState);

	int index    = params[1];
	int silence  = params[2];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	int draw_animation = 1;

	if ((params[0] / sizeof(cell)) >= 3)
	{
		draw_animation = params[3];
	}

	int weaponType  = get_pdata<int>(pWeapon, m_iId);
	int weaponState = get_pdata<int>(pWeapon, m_iWeaponState);
	int weaponNewState = weaponState;
	int animation = 0;

	switch (weaponType)
	{
		case CSW_M4A1:
		{
			if (silence)
			{
				weaponNewState |= WPNSTATE_M4A1_SILENCED;
				animation = M4A1_ATTACH_SILENCER;
			}
			else
			{
				weaponNewState &= ~WPNSTATE_M4A1_SILENCED;
				animation = M4A1_DETACH_SILENCER;
			}
			break;
		}
		case CSW_USP:
		{
			if (silence)
			{
				weaponNewState |= WPNSTATE_USP_SILENCED;
				animation = USP_ATTACH_SILENCER;
			}
			else
			{
				weaponNewState &= ~WPNSTATE_USP_SILENCED;
				animation = USP_DETACH_SILENCER;
			}
			break;
		}
	}

	if (weaponState != weaponNewState)
	{
		set_pdata<int>(pWeapon, m_iWeaponState, weaponNewState);

		edict_t *pPlayer = pWeapon->v.owner;

		if (draw_animation > 0 && UTIL_IsPlayer(pPlayer))
		{
			int currentWeapon = *static_cast<int *>(MF_PlayerPropAddr(TypeConversion.edict_to_id(pPlayer), Player_CurrentWeapon));

			if (currentWeapon != weaponType)
			{
				return 1;
			}

			pPlayer->v.weaponanim = animation;

			if (draw_animation >= 2)
			{
				// Skip if cl_lw client cvar (client-side weapon firing prediction) is set.
				// Technically, this should be associated to UseDecrement(), but it's true by default in game at compilation.
				if (!ENGINE_CANSKIP(pPlayer))
				{
					MESSAGE_BEGIN(MSG_ONE, SVC_WEAPONANIM, nullptr, pPlayer);
						WRITE_BYTE(animation);
						WRITE_BYTE(pWeapon->v.body);
					MESSAGE_END();
				}

				GET_OFFSET("CBasePlayer", m_szAnimExtention);
				GET_OFFSET("CBasePlayerWeapon", m_flTimeWeaponIdle);
				GET_OFFSET("CBasePlayerWeapon", m_flNextSecondaryAttack);
				GET_OFFSET("CBasePlayerWeapon", m_flNextPrimaryAttack);

				char animExt[12];
				float time = 0.0f;

				switch (weaponType)
				{
					case CSW_M4A1:
					{
						strcpy(animExt, "rifle");
						time = 2.0f;
						break;
					}
					case CSW_USP:
					{
						strcpy(animExt, "onehanded");
						time = 3.0f;
						break;
					}
				}

				set_pdata<const char*>(pPlayer, m_szAnimExtention, STRING(MAKE_STRING(animExt)));

				set_pdata<float>(pWeapon, m_flTimeWeaponIdle, time);
				set_pdata<float>(pWeapon, m_flNextSecondaryAttack, time);
				set_pdata<float>(pWeapon, m_flNextPrimaryAttack, time);
			}
		}

		return 1;
	}

	return 0;
}

// native cs_get_weapon_burst(index);
static cell AMX_NATIVE_CALL cs_get_weapon_burstmode(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerItem"  , m_iId);
	GET_OFFSET("CBasePlayerWeapon", m_iWeaponState);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	int flag = 0;

	switch (get_pdata<int>(pWeapon, m_iId))
	{
		case CSW_GLOCK18: flag = WPNSTATE_GLOCK18_BURST_MODE; break;
		case CSW_FAMAS:   flag = WPNSTATE_FAMAS_BURST_MODE;   break;
	}

	if (flag && get_pdata<int>(pWeapon, m_iWeaponState) & flag)
	{
		return 1;
	}

	return 0;
}

// native cs_set_weapon_burst(index, burstmode = 1);
static cell AMX_NATIVE_CALL cs_set_weapon_burstmode(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerItem"  , m_iId);
	GET_OFFSET("CBasePlayerWeapon", m_iWeaponState);

	int index = params[1];
	int burst = params[2];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	int weaponState = get_pdata<int>(pWeapon, m_iWeaponState);
	int weaponNewState = weaponState;

	switch (get_pdata<int>(pWeapon, m_iId))
	{
		case CSW_GLOCK18:
		{
			if (burst)
			{
				weaponNewState |= WPNSTATE_GLOCK18_BURST_MODE;
			}
			else
			{
				weaponNewState &= ~WPNSTATE_GLOCK18_BURST_MODE;
			}
			break;
		}
		case CSW_FAMAS:
		{
			if (burst)
			{
				weaponNewState |= WPNSTATE_FAMAS_BURST_MODE;
			}
			else
			{
				weaponNewState &= ~WPNSTATE_FAMAS_BURST_MODE;
			}
			break;
		}
	}

	if (weaponState != weaponNewState)
	{
		set_pdata<int>(pWeapon, m_iWeaponState, weaponNewState);

		if (UTIL_IsPlayer(pWeapon->v.owner))
		{
			UTIL_TextMsg_Generic(pWeapon->v.owner, burst ? "#Switch_To_BurstFire" : "#Switch_To_FullAuto");
		}

		return 1;
	}

	return 0;
}

// native cs_get_user_armor(index, &CsArmorType:armortype);
static cell AMX_NATIVE_CALL cs_get_user_armor(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iKevlar);

	int index = params[1];

	CHECK_PLAYER(index);

	edict_t *pPlayer = MF_GetPlayerEdict(index);
	cell *armorByRef = MF_GetAmxAddr(amx, params[2]);

	*armorByRef = get_pdata<int>(pPlayer, m_iKevlar);

	return static_cast<cell>(pPlayer->v.armorvalue);
}

// native cs_set_user_armor(index, armorvalue, CsArmorType:armortype);
static cell AMX_NATIVE_CALL cs_set_user_armor(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iKevlar);

	int index = params[1];
	int armor = params[2];
	int type  = params[3];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	pPlayer->v.armorvalue = armor;
	set_pdata<int>(pPlayer, m_iKevlar, type);

	if (type == CS_ARMOR_KEVLAR || type == CS_ARMOR_ASSAULTSUIT)
	{
		MESSAGE_BEGIN(MSG_ONE, MessageIdArmorType, nullptr, pPlayer);
			WRITE_BYTE(type == CS_ARMOR_ASSAULTSUIT ? 1 : 0);
		MESSAGE_END();
	}

	return 1;
}

// native cs_get_user_vip(index);
static cell AMX_NATIVE_CALL cs_get_user_vip(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bIsVIP);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bIsVIP))
	{
		return 1;
	}

	return 0;
}

// native cs_set_user_vip(index, vip = 1, model = 1, scoreboard = 1);
static cell AMX_NATIVE_CALL cs_set_user_vip(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bIsVIP);
	GET_OFFSET("CBasePlayer", m_iModelName);

	int index = params[1];
	bool vip  = params[2] != 0;

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	bool updateModel, updateScoreboard;

	if (params[0] / sizeof(cell) == 2)
	{
		updateModel = true;
		updateScoreboard = true;
	}
	else
	{
		updateModel = (params[3] == 1);
		updateScoreboard = (params[4] == 1);
	}

	set_pdata<bool>(pPlayer, m_bIsVIP, vip);

	if (updateModel)
	{
		CS_Internal_Models modelName = CS_CT_VIP;

		if (!vip)
		{
			CS_Internal_Models CTmodels[5] = { CS_CT_URBAN, CS_CT_GSG9, CS_CT_GIGN, CS_CT_SAS, CZ_CT_SPETSNAZ };
			modelName = CTmodels[RANDOM_LONG(0, 4)];
		}

		set_pdata<int>(pPlayer, m_iModelName, modelName);
		Players[index].ResetModel(pPlayer);
	}

	if (updateScoreboard)
	{
		int scoreattrib = SCOREATTRIB_VIP;

		if (!vip)
		{
			scoreattrib = (pPlayer->v.deadflag == DEAD_NO && pPlayer->v.health > 0) ? SCOREATTRIB_NOTHING : SCOREATTRIB_DEAD;
		}

		MESSAGE_BEGIN(MSG_ALL, MessageIdScoreAttrib);
			WRITE_BYTE(index);
			WRITE_BYTE(scoreattrib);
		MESSAGE_END();
	}

	return 1;
}

// native CsTeams:cs_get_user_team(index, &any:model = CS_DONTCHANGE);
static cell AMX_NATIVE_CALL cs_get_user_team(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iModelName);
	GET_OFFSET("CBasePlayer", m_iTeam);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if ((params[0] / sizeof(cell)) >= 2)
	{
		cell *modelByRef = MF_GetAmxAddr(amx, params[2]);
		*modelByRef = get_pdata<int>(pPlayer, m_iModelName);
	}

	return get_pdata<int>(pPlayer, m_iTeam);
}

// native cs_set_user_team(index, any:team, any:model = CS_DONTCHANGE, bool:send_teaminfo = true);
static cell AMX_NATIVE_CALL cs_set_user_team(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iModelName);
	GET_OFFSET("CBasePlayer", m_iTeam);

	int index = params[1];
	int team  = params[2];
	int model = params[3];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<int>(pPlayer, m_iTeam, team);

	if (model > 0)
	{
		set_pdata<int>(pPlayer, m_iModelName, model);
	}

	if (model >= 0)
	{
		Players[index].ResetModel(pPlayer);
	}

	bool sendTeamInfo = true;

	if (*params / sizeof(cell) >= 4)
	{
		sendTeamInfo = params[4] != 0;
	}

	char teaminfo[32];

	switch (team)
	{
		case TEAM_UNASSIGNED: strcpy(teaminfo, "UNASSIGNED"); break;
		case TEAM_T:          strcpy(teaminfo, "TERRORIST");  break;
		case TEAM_CT:         strcpy(teaminfo, "CT");         break;
		case TEAM_SPECTATOR:  strcpy(teaminfo, "SPECTATOR");  break;
		default:              sprintf(teaminfo, "TEAM_%i", team);
	}

	if (sendTeamInfo)
	{
		MESSAGE_BEGIN(MSG_ALL, MessageIdTeamInfo);
			WRITE_BYTE(index);
			WRITE_STRING(teaminfo);
		MESSAGE_END();
	}

	MF_SetPlayerTeamInfo(index, team, team <= TEAM_SPECTATOR ? teaminfo : nullptr);

	return 1;
}

// native cs_get_user_buyzone(index);
static cell AMX_NATIVE_CALL cs_get_user_inside_buyzone(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_signals);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<CUnifiedSignals>(pPlayer, m_signals).GetState() & SIGNAL_BUY)
	{
		return 1;
	}

	return 0;
}

// native cs_get_user_mapzones(index);
static cell AMX_NATIVE_CALL cs_get_user_mapzones(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_signals);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<CUnifiedSignals>(pPlayer, m_signals).GetSignal();
}

// native cs_get_user_plant(index);
static cell AMX_NATIVE_CALL cs_get_user_plant(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasC4);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bHasC4))
	{
		return 1;
	}

	return 0;
}

// native cs_set_user_plant(index, plant = 1, showbombicon = 1);
static cell AMX_NATIVE_CALL cs_set_user_plant(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasC4);

	int index  = params[1];
	bool plant = params[2] != 0;
	bool icon  = params[3] != 0;

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<bool>(pPlayer, m_bHasC4, plant);

	if (plant)
	{
		if (icon)
		{
			MESSAGE_BEGIN(MSG_ONE, MessageIdStatusIcon, nullptr, pPlayer);
				WRITE_BYTE(1);
				WRITE_STRING("c4");
				WRITE_BYTE(DEFUSER_COLOUR_R);
				WRITE_BYTE(DEFUSER_COLOUR_G);
				WRITE_BYTE(DEFUSER_COLOUR_B);
			MESSAGE_END();
		}
	}
	else
	{
		MESSAGE_BEGIN(MSG_ONE, MessageIdStatusIcon, nullptr, pPlayer);
			WRITE_BYTE(0);
			WRITE_STRING("c4");
		MESSAGE_END();
	}

	return 1;
}

// native cs_get_user_defuse(index);
static cell AMX_NATIVE_CALL cs_get_user_defusekit(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasDefuser);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bHasDefuser))
	{
		return 1;
	}

	return 0;
}

// native cs_set_user_defuse(index, defusekit = 1, r = 0, g = 160, b = 0, icon[] = "defuser", flash = 0);
static cell AMX_NATIVE_CALL cs_set_user_defusekit(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasDefuser);

	int index = params[1];
	bool kit  = params[2] != 0;

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<bool>(pPlayer, m_bHasDefuser, kit);
	pPlayer->v.body = kit ? 1 : 0;

	if (kit)
	{
		int colour[3] = {DEFUSER_COLOUR_R, DEFUSER_COLOUR_G, DEFUSER_COLOUR_B};

		for (int i = 0; i < 3; i++)
		{
			if (params[i + 3] != -1)
			{
				colour[i] = params[i + 3];
			}
		}

		const char* icon = "defuser";

		if (params[6] != -1)
		{
			int length;
			icon = MF_GetAmxString(amx, params[6], 1, &length);
		}

		MESSAGE_BEGIN(MSG_ONE, MessageIdStatusIcon, nullptr, pPlayer);
			WRITE_BYTE(params[7] == 1 ? 2 : 1);
			WRITE_STRING(icon);
			WRITE_BYTE(colour[0]);
			WRITE_BYTE(colour[1]);
			WRITE_BYTE(colour[2]);
		MESSAGE_END();
	}
	else
	{
		MESSAGE_BEGIN(MSG_ONE, MessageIdStatusIcon, nullptr, pPlayer);
			WRITE_BYTE(0);
			WRITE_STRING("defuser");
		MESSAGE_END();
	}

	return 1;
}

// native cs_get_user_bpammo(index, weapon);
static cell AMX_NATIVE_CALL cs_get_user_backpackammo(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_rgAmmo);

	int index    = params[1];
	int weaponId = params[2];

	CHECK_PLAYER(index);

	int ammoIndex;

	if (weaponId <= CSW_NONE || weaponId >= MAX_WEAPONS || (ammoIndex = WeaponsList[weaponId].ammoIndex1) < 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid weapon id %d", weaponId);
		return 0;
	}

	return get_pdata<int>(MF_GetPlayerEdict(index), m_rgAmmo, ammoIndex);
}

// native cs_set_user_bpammo(index, weapon, amount);
static cell AMX_NATIVE_CALL cs_set_user_backpackammo(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_rgAmmo);

	int index    = params[1];
	int weaponId = params[2];
	int amount   = params[3];

	CHECK_PLAYER(index);

	int ammoIndex;

	if (weaponId <= CSW_NONE || weaponId >= MAX_WEAPONS || (ammoIndex = WeaponsList[weaponId].ammoIndex1) < 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid weapon id %d", weaponId);
		return 0;
	}

	set_pdata<int>(MF_GetPlayerEdict(index), m_rgAmmo, amount, ammoIndex);

	return 1;
}

// native cs_get_user_nvg(index);
static cell AMX_NATIVE_CALL cs_get_user_nvg(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasNightVision);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bHasNightVision))
	{
		return 1;
	}

	return 0;
}

// native cs_set_user_nvg(index, nvgoggles = 1);
static cell AMX_NATIVE_CALL cs_set_user_nvg(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasNightVision);

	int index = params[1];
	bool nvg  = params[2] != 0;

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (nvg && get_pdata<bool>(pPlayer, m_bHasNightVision))
	{
		UTIL_TextMsg_Generic(pPlayer, "#Already_Have_One");
	}
	else
	{
		set_pdata<bool>(pPlayer, m_bHasNightVision, nvg);
	}

	return 1;
}

// native cs_get_user_model(index, model[], len);
static cell AMX_NATIVE_CALL cs_get_user_model(AMX *amx, cell *params)
{
	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return MF_SetAmxString(amx, params[2], GETCLIENTKEYVALUE(GETINFOKEYBUFFER(pPlayer), "model"), params[3]);
}

// native cs_set_user_model(index, const model[], bool:update_index = false);
static cell AMX_NATIVE_CALL cs_set_user_model(AMX *amx, cell *params)
{
	int index = params[1];
	int model = params[2];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (model == -1)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid model %d", params[2]);
		return 0;
	}

	int length;
	const char *newModel = MF_GetAmxString(amx, params[2], 0, &length);

	if (!*newModel)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Model can not be empty");
		return 0;
	}

	Players[index].SetModel(newModel);
	Players[index].UpdateModel(pPlayer);

	if (*params / sizeof(cell) >= 3 && params[3] != 0)
	{
		if (!HasReHlds && !Server)
		{
			MF_Log("cs_set_user_model is disabled with update_index parameter set");
			return 0;
		}

		if (!ModelsList.elements())
		{
			auto numResources = HasReHlds ? RehldsData->GetResourcesNum() : Server->num_resources;

			if (numResources)
			{
				for (auto i = 0; i < numResources; ++i) // Saves all the precached models into a list.
				{
					auto resource = HasReHlds ? RehldsData->GetResource(i) : &Server->resourcelist[i];

					if (resource->type == t_model)
					{
						ModelsList.insert(resource->szFileName, resource->nIndex);
					}
				}
			}
		}

		GET_OFFSET("CBasePlayer", m_modelIndexPlayer);

		char modelpath[PLATFORM_MAX_PATH];
		ke::SafeSprintf(modelpath, sizeof(modelpath), "models/player/%s/%s.mdl", newModel, newModel);

		auto modelIndex = 0;

		if (ModelsList.retrieve(modelpath, &modelIndex))
		{
			if (pPlayer->v.modelindex != modelIndex)
			{
				SET_MODEL(pPlayer, STRING(ALLOC_STRING(modelpath)));
			}

			set_pdata<int>(pPlayer, m_modelIndexPlayer, modelIndex);
			return 1;
		}

		MF_Log("Model must be precached using cs_set_user_model with update_index parameter set");
		return 0;
	}

	return 1;
}

// native cs_reset_user_model(index);
static cell AMX_NATIVE_CALL cs_reset_user_model(AMX *amx, cell *params)
{
	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	Players[index].ResetModel(pPlayer);

	return 1;
}

// native cs_get_hostage_foll(index);
static cell AMX_NATIVE_CALL cs_get_hostage_follow(AMX *amx, cell *params)
{
	GET_OFFSET("CBaseMonster", m_hTargetEnt);
	GET_OFFSET("CHostage"    , m_improv    );

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t* pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);

	void *pImprov = get_pdata<void*>(pHostage, m_improv);
	edict_t *pEntity = nullptr;

	if (pImprov) // Specific to CZ
	{
		GET_OFFSET("CHostageImprov", m_behavior);
		GET_OFFSET("CHostageImprov", m_followState);
		GET_OFFSET("SimpleStateMachine", m_state); // +4 for virtual table pointer of IImprovEvent.
		GET_OFFSET("HostageFollowState", m_leader);

		if (get_pdata<void*>(pImprov, m_behavior + 4 + m_state) == reinterpret_cast<int8*>(pImprov) + m_followState)
		{
			pEntity = get_pdata<EHANDLE>(pImprov, m_followState + m_leader).Get();
		}
	}
	else
	{
		pEntity = get_pdata<EHANDLE>(pHostage, m_hTargetEnt).Get();
	}

	return pEntity ? TypeConversion.edict_to_id(pEntity) : 0;
}

// native cs_set_hostage_foll(index, followedindex = 0);
static cell AMX_NATIVE_CALL cs_set_hostage_follow(AMX *amx, cell *params)
{
	GET_OFFSET("CBaseMonster", m_hTargetEnt);
	GET_OFFSET("CHostage"    , m_improv    );

	int index  = params[1];
	int target = params[2];

	CHECK_NONPLAYER(index);
	edict_t* pHostage = TypeConversion.id_to_edict(index);

	if (target != 0)
	{
		CHECK_ENTITY(target);
	}

	CHECK_HOSTAGE(pHostage);

	void *pImprov = get_pdata<void*>(pHostage, m_improv);

	if (pImprov) // Specific to CZ
	{
		GET_OFFSET("CHostageImprov", m_behavior);
		GET_OFFSET("CHostageImprov", m_followState);
		GET_OFFSET("CHostageImprov", m_idleState);
		GET_OFFSET("HostageFollowState", m_leader);
		GET_OFFSET("SimpleStateMachine", m_state);      // +4 for virtual table pointer of IImprovEvent.
		GET_OFFSET("SimpleStateMachine", m_stateTimer); //

		if (target)
		{
			set_pdata<void*>(pImprov, m_behavior + 4 + m_state, reinterpret_cast<int8*>(pImprov) + m_followState);
			set_pdata<float>(pImprov, m_behavior + 4 + m_stateTimer, gpGlobals->time);

			get_pdata<EHANDLE>(pImprov, m_followState + m_leader).Set(TypeConversion.id_to_edict(target));
		}
		else
		{
			set_pdata<void*>(pImprov, m_behavior + 4 + m_state, reinterpret_cast<int8*>(pImprov) + m_idleState);
			set_pdata<float>(pImprov, m_behavior + 4 + m_stateTimer, gpGlobals->time);

			get_pdata<EHANDLE>(pImprov, m_followState + m_leader).Set(nullptr);
		}
	}
	else
	{
		get_pdata<EHANDLE>(pHostage, m_hTargetEnt).Set(target ? TypeConversion.id_to_edict(target) : nullptr);
	}

	return 1;
}

// native cs_get_weapon_ammo(index);
static cell AMX_NATIVE_CALL cs_get_weapon_ammo(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerWeapon", m_iClip);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	return get_pdata<int>(pWeapon, m_iClip);
}

// native cs_set_weapon_ammo(index, newammo);
static cell AMX_NATIVE_CALL cs_set_weapon_ammo(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayerWeapon", m_iClip);

	int index = params[1];
	int ammo  = params[2];

	CHECK_NONPLAYER(index);
	edict_t *pWeapon = TypeConversion.id_to_edict(index);

	set_pdata<int>(pWeapon, m_iClip, ammo);

	return 1;
}

// native cs_get_user_hasprim(index);
static cell AMX_NATIVE_CALL cs_get_user_hasprimary(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bHasPrimary);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bHasPrimary))
	{
		return 1;
	}

	return 0;
}

// native cs_get_no_knives();
static cell AMX_NATIVE_CALL cs_get_no_knives(AMX *amx, cell *params)
{
	return NoKnivesMode ? 1 : 0;
}

// native cs_set_no_knives(noknives = 0);
static cell AMX_NATIVE_CALL cs_set_no_knives(AMX *amx, cell *params)
{
	if (!HasReGameDll && !GiveDefaultItemsDetour)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_set_no_knives() is disabled. Check your amxx logs.");
		return 0;
	}

	NoKnivesMode = params[1] != 0;

	ToggleHook_GiveDefaultItems(NoKnivesMode);

	return 1;
}

// native cs_get_user_tked(index);
static cell AMX_NATIVE_CALL cs_get_user_tked(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bJustKilledTeammate);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bJustKilledTeammate))
	{
		return 1;
	}

	return 0;
}

// native cs_set_user_tked(index, tk = 1, subtract = 1);
static cell AMX_NATIVE_CALL cs_set_user_tked(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bJustKilledTeammate);
	GET_OFFSET("CBasePlayer", m_iTeam);
	GET_OFFSET("CBasePlayer", m_iDeaths);

	int index    = params[1];
	int tk       = params[2];
	int subtract = params[3];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<bool>(pPlayer, m_bJustKilledTeammate, tk != 0);

	if (subtract > 0)
	{
		pPlayer->v.frags -= subtract;

		MESSAGE_BEGIN(MSG_ALL, MessageIdScoreInfo);
			WRITE_BYTE(index);
			WRITE_SHORT(static_cast<int>(pPlayer->v.frags));
			WRITE_SHORT(get_pdata<int>(pPlayer, m_iDeaths));
			WRITE_SHORT(0);
			WRITE_SHORT(get_pdata<int>(pPlayer, m_iTeam));
		MESSAGE_END();
	}

	return 1;
}

// native cs_get_user_driving(index);
static cell AMX_NATIVE_CALL cs_get_user_driving(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iTrain);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<int>(pPlayer, m_iTrain);
}

// native cs_get_user_stationary(index);
static cell AMX_NATIVE_CALL cs_get_user_stationary(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iClientHideHUD);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<int>(pPlayer, m_iClientHideHUD);
}

// native cs_get_user_shield(index);
static cell AMX_NATIVE_CALL cs_get_user_shield(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_bOwnsShield);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	if (get_pdata<bool>(pPlayer, m_bOwnsShield))
	{
		return 1;
	}

	return 0;
}

// native cs_user_spawn(player);
static cell AMX_NATIVE_CALL cs_user_spawn(AMX *amx, cell *params)
{
	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	pPlayer->v.deadflag = DEAD_RESPAWNABLE;
	MDLL_Think(pPlayer);

	if (MF_IsPlayerBot(index) && pPlayer->v.deadflag == DEAD_RESPAWNABLE)
	{
		MDLL_Spawn(pPlayer);
	}

	return 1;
}

// native cs_get_armoury_type(index, &count = 1);
static cell AMX_NATIVE_CALL cs_get_armoury_type(AMX *amx, cell *params)
{
	GET_OFFSET("CArmoury", m_iItem);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pArmoury = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pArmoury->v.classname), "armoury_entity"))
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Not an armoury_entity! (%d)", index);
		return 0;
	}

	int weapontype = get_pdata<int>(pArmoury, m_iItem);;
	int weapontype_out = 0;

	switch (weapontype)
	{
		case CSA_MP5NAVY:		weapontype_out = CSW_MP5NAVY;      break;
		case CSA_TMP:			weapontype_out = CSW_TMP;          break;
		case CSA_P90:			weapontype_out = CSW_P90;          break;
		case CSA_MAC10:			weapontype_out = CSW_MAC10;        break;
		case CSA_AK47:			weapontype_out = CSW_AK47;         break;
		case CSA_SG552:			weapontype_out = CSW_SG552;        break;
		case CSA_M4A1:			weapontype_out = CSW_M4A1;         break;
		case CSA_AUG:			weapontype_out = CSW_AUG;          break;
		case CSA_SCOUT:			weapontype_out = CSW_SCOUT;        break;
		case CSA_G3SG1:			weapontype_out = CSW_G3SG1;        break;
		case CSA_AWP:			weapontype_out = CSW_AWP;          break;
		case CSA_M3:			weapontype_out = CSW_M3;           break;
		case CSA_XM1014:		weapontype_out = CSW_XM1014;       break;
		case CSA_M249:			weapontype_out = CSW_M249;         break;
		case CSA_FLASHBANG:		weapontype_out = CSW_FLASHBANG;    break;
		case CSA_HEGRENADE:		weapontype_out = CSW_HEGRENADE;    break;
		case CSA_VEST:			weapontype_out = CSW_VEST;         break;
		case CSA_VESTHELM:		weapontype_out = CSW_VESTHELM;     break;
		case CSA_SMOKEGRENADE:	weapontype_out = CSW_SMOKEGRENADE; break;
		default:
		{
			MF_LogError(amx, AMX_ERR_NATIVE, "Unexpected weapon type of %d!", index);
			return 0;
		}
	}

	if (*params / sizeof(cell) >= 2)
	{
		GET_OFFSET("CArmoury", m_iCount);

		*MF_GetAmxAddr(amx, params[2]) = get_pdata<int>(pArmoury, m_iCount);
	}

	return weapontype_out;
}

// native cs_set_armoury_type(index, type, count = -1);
static cell AMX_NATIVE_CALL cs_set_armoury_type(AMX *amx, cell *params)
{
	GET_OFFSET("CArmoury", m_iItem);

	int index = params[1];
	int type  = params[2];

	CHECK_NONPLAYER(index);
	edict_t *pArmoury = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pArmoury->v.classname), "armoury_entity"))
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Not an armoury_entity! (%d)", index);
		return 0;
	}

	int weapontype;

	switch (type)
	{
		case CSW_MP5NAVY:		weapontype = CSA_MP5NAVY;      break;
		case CSW_TMP:			weapontype = CSA_TMP;          break;
		case CSW_P90:			weapontype = CSA_P90;          break;
		case CSW_MAC10:			weapontype = CSA_MAC10;        break;
		case CSW_AK47:			weapontype = CSA_AK47;         break;
		case CSW_SG552:			weapontype = CSA_SG552;        break;
		case CSW_M4A1:			weapontype = CSA_M4A1;         break;
		case CSW_AUG:			weapontype = CSA_AUG;          break;
		case CSW_SCOUT:			weapontype = CSA_SCOUT;        break;
		case CSW_G3SG1:			weapontype = CSA_G3SG1;        break;
		case CSW_AWP:			weapontype = CSA_AWP;          break;
		case CSW_M3:			weapontype = CSA_M3;           break;
		case CSW_XM1014:		weapontype = CSA_XM1014;       break;
		case CSW_M249:			weapontype = CSA_M249;         break;
		case CSW_FLASHBANG:		weapontype = CSA_FLASHBANG;    break;
		case CSW_HEGRENADE:		weapontype = CSA_HEGRENADE;    break;
		case CSW_VEST:			weapontype = CSA_VEST;         break;
		case CSW_VESTHELM:		weapontype = CSA_VESTHELM;     break;
		case CSW_SMOKEGRENADE:	weapontype = CSA_SMOKEGRENADE; break;
		default:
		{
			MF_LogError(amx, AMX_ERR_NATIVE, "Unsupported weapon type! (%d)", type);
			return 0;
		}
	}

	set_pdata<int>(pArmoury, m_iItem, weapontype);

	if (*params / sizeof(cell) >= 3)
	{
		GET_OFFSET("CArmoury", m_iCount);
		GET_OFFSET("CArmoury", m_iInitialCount);

		int count = params[3];

		if (count >= 0)
		{
			if (!count)
			{
				pArmoury->v.effects |= EF_NODRAW;
			}
			else
			{
				pArmoury->v.effects &= ~EF_NODRAW;
			}

			set_pdata<int>(pArmoury, m_iCount, count);
			set_pdata<int>(pArmoury, m_iInitialCount, count);
		}
	}

	return 1;
}

// native cs_set_user_zoom(index, type, mode);
static cell AMX_NATIVE_CALL cs_set_user_zoom(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iFOV);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	int type = params[2];
	int mode = params[3];
	int weapon = *static_cast<int *>(MF_PlayerPropAddr(index, Player_CurrentWeapon));

	CPlayer& player = Players[index];

	if (player.GetZoom())
	{
		DisableMessageHooks();
		player.ResetZoom();
	}

	if (type == CS_RESET_ZOOM)
	{
		set_pdata<int>(pPlayer, m_iFOV, CS_NO_ZOOM);
		return 1;
	}

	int value;

	switch (type)
	{
		case CS_SET_NO_ZOOM:
		{
			value = CS_NO_ZOOM;
			break;
		}
		case CS_SET_FIRST_ZOOM:
		{
			value = CS_FIRST_ZOOM;
			break;
		}
		case CS_SET_SECOND_ZOOM:
		{
			if (weapon == CSW_G3SG1 || weapon == CSW_SG550 || weapon == CSW_SCOUT)
			{
				value = CS_SECOND_NONAWP_ZOOM;
			}
			else
			{
				value = CS_SECOND_AWP_ZOOM;
			}
			break;
		}
		case CS_SET_AUGSG552_ZOOM:
		{
			value = CS_AUGSG552_ZOOM;
			break;
		}
		default:
		{
			MF_LogError(amx, AMX_ERR_NATIVE, "Invalid zoom type %d", type);
			return 0;
		}
	}

	if (!mode)
	{
		player.SetZoom(value);
		EnableMessageHooks();
	}

	set_pdata<int>(pPlayer, m_iFOV, value);

	return 1;
}

// native cs_get_user_zoom(index);
static cell AMX_NATIVE_CALL cs_get_user_zoom(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iFOV);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	switch (get_pdata<int>(pPlayer, m_iFOV))
	{
		case CS_NO_ZOOM:
		{
			return CS_SET_NO_ZOOM;
		}
		case CS_FIRST_ZOOM:
		{
			return CS_SET_FIRST_ZOOM;
		}
		case CS_SECOND_AWP_ZOOM:
		case CS_SECOND_NONAWP_ZOOM:
		{
			return CS_SET_SECOND_ZOOM;
		}
		case CS_AUGSG552_ZOOM:
		{
			return CS_SET_AUGSG552_ZOOM;
		}
	}

	return 0;
}

// native cs_get_user_submodel(index);
static cell AMX_NATIVE_CALL cs_get_user_submodel(AMX* amx, cell* params)
{
	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return pPlayer->v.body;
}

// native cs_set_user_submodel(index, value);
static cell AMX_NATIVE_CALL cs_set_user_submodel(AMX* amx, cell* params)
{
	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	pPlayer->v.body = params[2];

	return 1;
}

// native Float:cs_get_user_lastactivity(index);
static cell AMX_NATIVE_CALL cs_get_user_lastactivity(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_fLastMovement);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return amx_ftoc(get_pdata<float>(pPlayer, m_fLastMovement));
}

// native cs_set_user_lastactivity(index, Float:value);
static cell AMX_NATIVE_CALL cs_set_user_lastactivity(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_fLastMovement);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<float>(pPlayer, m_fLastMovement, amx_ctof(params[2]));

	return 1;
}

// native cs_get_user_hostagekills(index);
static cell AMX_NATIVE_CALL cs_get_user_hostagekills(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iHostagesKilled);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	return get_pdata<int>(pPlayer, m_iHostagesKilled);
}

// native cs_set_user_hostagekills(index, value);
static cell AMX_NATIVE_CALL cs_set_user_hostagekills(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_iHostagesKilled);

	int index = params[1];

	CHECK_PLAYER(index);
	edict_t *pPlayer = MF_GetPlayerEdict(index);

	set_pdata<int>(pPlayer, m_iHostagesKilled, params[2]);

	return 1;
}

// native Float:cs_get_hostage_lastuse(index);
static cell AMX_NATIVE_CALL cs_get_hostage_lastuse(AMX *amx, cell *params)
{
	GET_OFFSET("CHostage", m_flPathAcquired);
	GET_OFFSET("CHostage", m_improv);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);

	void *pImprov = get_pdata<void*>(pHostage, m_improv);

	if (pImprov) // Specific to CZ
	{
		GET_OFFSET("CHostageImprov", m_behavior);
		GET_OFFSET("SimpleStateMachine", m_stateTimer); // +4 for virtual table pointer of IImprovEvent.

		return amx_ftoc(get_pdata<float>(pImprov, m_behavior + 4 + m_stateTimer + 0)); // m_timestamp
	}

	return amx_ftoc(get_pdata<float>(pHostage, m_flPathAcquired));
}

// native cs_set_hostage_lastuse(index, Float:value);
static cell AMX_NATIVE_CALL cs_set_hostage_lastuse(AMX *amx, cell *params)
{
	GET_OFFSET("CHostage", m_flPathAcquired);
	GET_OFFSET("CHostage", m_improv);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);
 
	void *pImprov = get_pdata<void*>(pHostage, m_improv);

	if (pImprov) // Specific to CZ
	{
		GET_OFFSET("CHostageImprov", m_behavior);
		GET_OFFSET("SimpleStateMachine", m_stateTimer); // +4 for virtual table pointer of IImprovEvent.

		return amx_ftoc(get_pdata<float>(pImprov, m_behavior + 4 + m_stateTimer + 0)); // m_timestamp

	}
	set_pdata<float>(pHostage, m_flPathAcquired, amx_ctof(params[2]));

	return 1;
}

// native Float:cs_get_hostage_nextuse(index);
static cell AMX_NATIVE_CALL cs_get_hostage_nextuse(AMX* amx, cell* params)
{
	GET_OFFSET("CHostage", m_flNextChange);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);

	return amx_ftoc(get_pdata<float>(pHostage, m_flNextChange));
}

// native cs_set_hostage_nextuse(index, Float:value);
static cell AMX_NATIVE_CALL cs_set_hostage_nextuse(AMX* amx, cell* params)
{
	GET_OFFSET("CHostage", m_flNextChange);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pHostage = TypeConversion.id_to_edict(index);

	CHECK_HOSTAGE(pHostage);

	set_pdata<float>(pHostage, m_flNextChange, amx_ctof(params[2]));

	return 1;
}

// native Float:cs_get_c4_explode_time(index);
static cell AMX_NATIVE_CALL cs_get_c4_explode_time(AMX* amx, cell* params)
{
	GET_OFFSET("CGrenade", m_flC4Blow);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pC4 = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pC4->v.classname), "grenade") != 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Entity %d (\"%s\") is not C4!", index, STRING(pC4->v.classname));
		return 0;
	}

	return amx_ftoc(get_pdata<float>(pC4, m_flC4Blow));
}

// native cs_set_c4_explode_time(index, Float:value);
static cell AMX_NATIVE_CALL cs_set_c4_explode_time(AMX* amx, cell* params)
{
	GET_OFFSET("CGrenade", m_flC4Blow);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pC4 = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pC4->v.classname), "grenade") != 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Entity %d (\"%s\") is not C4!", index, STRING(pC4->v.classname));
		return 0;
	}

	set_pdata<float>(pC4, m_flC4Blow, amx_ctof(params[2]));

	return 1;
}

// native bool:cs_get_c4_defusing(c4index);
static cell AMX_NATIVE_CALL cs_get_c4_defusing(AMX* amx, cell* params)
{
	GET_OFFSET("CGrenade", m_bStartDefuse);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pC4 = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pC4->v.classname), "grenade") != 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Entity %d (\"%s\") is not C4!", index, STRING(pC4->v.classname));
		return 0;
	}

	return get_pdata<bool>(pC4, m_bStartDefuse) ? 1 : 0;
}

// native cs_set_c4_defusing(c4index, bool:defusing);
static cell AMX_NATIVE_CALL cs_set_c4_defusing(AMX* amx, cell* params)
{
	GET_OFFSET("CGrenade", m_bStartDefuse);

	int index = params[1];

	CHECK_NONPLAYER(index);
	edict_t *pC4 = TypeConversion.id_to_edict(index);

	if (strcmp(STRING(pC4->v.classname), "grenade") != 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Entity %d (\"%s\") is not C4!", index, STRING(pC4->v.classname));
		return 0;
	}

	set_pdata<bool>(pC4, m_bStartDefuse, params[2] != 0);

	return 1;
}

// cs_create_entity(const classname[])
static cell AMX_NATIVE_CALL cs_create_entity(AMX* amx, cell* params)
{
	if (!CS_CreateNamedEntity)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_create_entity() is disabled. Check your amxx logs.");
		return 0;
	}

	int len;
	int iszClass = ALLOC_STRING(MF_GetAmxString(amx, params[1], 0, &len));

	edict_t *pEnt = CS_CreateNamedEntity(iszClass);

	if (!FNullEnt(pEnt))
	{
		return TypeConversion.edict_to_id(pEnt);
	}

	return 0;
}

// cs_find_ent_by_class(start_index, const classname[])
static cell AMX_NATIVE_CALL cs_find_ent_by_class(AMX* amx, cell* params)
{
	if (!CS_UTIL_FindEntityByString)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_find_ent_by_class() is disabled. Check your amxx logs.");
		return 0;
	}

	int len;
	auto pEntity = (CBaseEntity*)TypeConversion.id_to_cbase(params[1]);
	const char* value = MF_GetAmxString(amx, params[2], 0, &len);

	int index = TypeConversion.cbase_to_id(CS_UTIL_FindEntityByString(pEntity, "classname", value));

	if (index != -1)
	{
		return index;
	}

	return 0;
}

// cs_find_ent_by_owner(start_index, const classname[], owner)
static cell AMX_NATIVE_CALL cs_find_ent_by_owner(AMX* amx, cell* params)
{
	if (!CS_UTIL_FindEntityByString)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_find_ent_by_owner() is disabled. Check your amxx logs.");
		return 0;
	}

	int owner = params[3];
	CHECK_ENTITY_SIMPLE(owner);

	int length;
	auto pEntity = (CBaseEntity*)TypeConversion.id_to_cbase(params[1]);
	const char* value = MF_GetAmxString(amx, params[2], 0, &length);

	edict_t *pOwner = TypeConversion.id_to_edict(owner);

	while ((pEntity = CS_UTIL_FindEntityByString(pEntity, "classname", value)))
	{
		edict_t *pev = TypeConversion.cbase_to_edict(pEntity);

		if (!FNullEnt(pev) && pev->v.owner == pOwner)
		{
			int index = TypeConversion.edict_to_id(pev);

			if (index != -1)
			{
				return index;
			}
		}
	}

	return 0;
}

// cs_set_ent_class(index, const classname[])
static cell AMX_NATIVE_CALL cs_set_ent_class(AMX* amx, cell* params)
{
	if (!AddEntityHashValue || !RemoveEntityHashValue)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_set_ent_class() is disabled. Check your amxx logs.");
		return 0;
	}

	auto entity = params[1];
	CHECK_ENTITY_SIMPLE(entity);

	auto pev = TypeConversion.id_to_entvars(entity);

	if (pev->classname)
	{
		RemoveEntityHashValue(pev, STRING(pev->classname), CLASSNAME);
	}

	int length;
	auto new_classname = MF_GetAmxString(amx, params[2], 0, &length);

	pev->classname = ALLOC_STRING(new_classname);
	AddEntityHashValue(pev, STRING(pev->classname), CLASSNAME);

	return 1;
}

// native any:cs_get_item_id(const name[], &CsWeaponClassType:classid = CS_WEAPONCLASS_NONE);
static cell AMX_NATIVE_CALL cs_get_item_id(AMX* amx, cell* params)
{
	if (ItemsManager.HasConfigError())
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_get_item_id() is disabled because of corrupted or missing gamedata");
		return 0;
	}

	int length;
	char *name = MF_GetAmxString(amx, params[1], 0, &length);
	cell *classid = MF_GetAmxAddr(amx, params[2]);

	if (length)
	{
		AliasInfo info;

		if (ItemsManager.GetAliasInfosFromName(name, &info))
		{
			*classid = info.classid;
			return info.itemid;
		}
	}

	return CSI_NONE;
}

// native bool:cs_get_item_alias(itemid, name[], name_maxlen, altname[] = "", altname_maxlen = 0); 
static cell AMX_NATIVE_CALL cs_get_item_alias(AMX* amx, cell* params)
{
	if (ItemsManager.HasConfigError())
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_get_item_alias() is disabled because of corrupted or missing gamedata");
		return 0;
	}

	auto itemid = params[1];

	if (itemid == CSI_SHIELDGUN)
	{
		itemid = CSI_SHIELD;
	}
	else if (itemid == CSI_GLOCK)
	{
		itemid = CSI_GLOCK18;
	}
	else if (itemid <= CSI_NONE || itemid >= CSI_MAX_COUNT)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid item id: %d", itemid);
		return FALSE;
	}

	ke::AString name, altname;
	auto result = ItemsManager.GetAliasFromId(itemid, name, altname);

	MF_SetAmxString(amx, params[2], name.chars(), params[3]);
	MF_SetAmxString(amx, params[4], altname.chars(), params[5]);

	return result ? TRUE : FALSE;
}

// native bool:cs_get_translated_item_alias(const alias[], itemname[], maxlength);
static cell AMX_NATIVE_CALL cs_get_translated_item_alias(AMX* amx, cell* params)
{
	if (ItemsManager.HasConfigError())
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_get_translated_item_alias() is disabled because of corrupted or missing gamedata");
		return 0;
	}

	int length;
	const char *alias = MF_GetAmxString(amx, params[1], 0, &length);
	const char *name = alias;
	AliasInfo info;

	if (length && ItemsManager.GetAliasInfos(alias, &info) && info.itemid != CSI_NONE)
	{
		switch (info.itemid)
		{
			case CSI_VEST:
			case CSI_VESTHELM:
			case CSI_DEFUSER:
			case CSI_SHIELD:
			{
				// Special item_* defined in gamdata file as game
				// doesn't give us really a way to know about their classname
				// and I don't want to hard code them in module.
				name = info.classname.chars();
				break;
			}
			default:
			{
				// weapon_* retrieved from WeaponList messages at map change.
				name = WeaponsList[info.itemid].name.chars();
				break;
			}
		}
	}

	MF_SetAmxString(amx, params[2], name, params[3]);

	return info.itemid != CSI_NONE;
}

// native cs_get_weapon_info(weapon_id, CsWeaponInfo:type);
static cell AMX_NATIVE_CALL cs_get_weapon_info(AMX* amx, cell* params)
{
	if (!HasReGameDll && !GetWeaponInfo)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Native cs_get_weapon_info() is disabled. Check your amxx logs.");
		return 0;
	}

	int weapon_id = params[1];
	int info_type = params[2];

	WeaponInfoStruct *info; 

	if (weapon_id <= CSW_NONE || weapon_id > CSW_LAST_WEAPON || !(info = HasReGameDll ? ReGameApi->GetWeaponInfo(weapon_id) : GetWeaponInfo(weapon_id)))
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid weapon id: %d", weapon_id);
		return 0;
	}

	switch (info_type)
	{
		case CS_WEAPONINFO_COST:
		{
			return info->cost;
		}
		case CS_WEAPONINFO_CLIP_COST:
		{
			return info->clipCost;
		}
		case CS_WEAPONINFO_BUY_CLIP_SIZE:
		{
			return info->buyClipSize;
		}
		case CS_WEAPONINFO_GUN_CLIP_SIZE:
		{
			return info->gunClipSize;
		}
		case CS_WEAPONINFO_MAX_ROUNDS:
		{
			return info->maxRounds;
		}
		case CS_WEAPONINFO_AMMO_TYPE:
		{
			return info->ammoType;
		}
		// TODO: Ammo name (custom)
	}

	MF_LogError(amx, AMX_ERR_NATIVE, "Invalid info type: %d", info_type);
	return 0;
}

// native cs_get_user_weapon_entity(playerIndex);
static cell AMX_NATIVE_CALL cs_get_user_weapon_entity(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_pActiveItem);

	int playerIndex = params[1];

	CHECK_PLAYER(playerIndex);
	edict_t *pPlayer = MF_GetPlayerEdict(playerIndex);

	int weaponEntIndex = TypeConversion.cbase_to_id(get_pdata<void *>(pPlayer, m_pActiveItem));

	return (weaponEntIndex != -1) ? weaponEntIndex : 0;
}

// native cs_get_user_weapon(playerIndex, &clip = 0, &ammo = 0);
static cell AMX_NATIVE_CALL cs_get_user_weapon(AMX *amx, cell *params)
{
	GET_OFFSET("CBasePlayer", m_pActiveItem);
	GET_OFFSET("CBasePlayer", m_rgAmmo);
	GET_OFFSET("CBasePlayerItem", m_iId);
	GET_OFFSET("CBasePlayerWeapon", m_iClip);
	GET_OFFSET("CBasePlayerWeapon", m_iPrimaryAmmoType);

	int playerIndex = params[1];

	CHECK_PLAYER(playerIndex);
	edict_t *pPlayer = MF_GetPlayerEdict(playerIndex);

	edict_t *pWeapon = TypeConversion.cbase_to_edict(get_pdata<void *>(pPlayer, m_pActiveItem));

	if (!FNullEnt(pWeapon))
	{
		cell *cpTemp = MF_GetAmxAddr(amx, params[2]);
		*cpTemp = get_pdata<int>(pWeapon, m_iClip);

		cpTemp = MF_GetAmxAddr(amx, params[3]);
		int iAmmoType = get_pdata<int>(pWeapon, m_iPrimaryAmmoType);

		if(iAmmoType <= 0)
		{
			*cpTemp = 0;
		}
		else
		{
			*cpTemp = get_pdata<int>(pPlayer, m_rgAmmo, iAmmoType);
		}

		return get_pdata<int>(pWeapon, m_iId);
	}

	return 0;
}

// native cs_get_weaponbox_item(weaponboxIndex);
static cell AMX_NATIVE_CALL cs_get_weaponbox_item(AMX *amx, cell *params)
{
	GET_OFFSET("CWeaponBox", m_rgpPlayerItems);

	int weaponboxIndex = params[1];
	CHECK_NONPLAYER(weaponboxIndex);
	edict_t *pWeaponBox = TypeConversion.id_to_edict(weaponboxIndex);

	if (strcmp(STRING(pWeaponBox->v.classname), "weaponbox") != 0)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Not a weaponbox entity! (%d)", weaponboxIndex);
		return 0;
	}

	edict_t *pWeapon;
	for (int i = 1; i < MAX_ITEM_TYPES; i++)
	{
		pWeapon = TypeConversion.cbase_to_edict(get_pdata<void *>(pWeaponBox, m_rgpPlayerItems, i));
		if (!FNullEnt(pWeapon))
		{
			return TypeConversion.edict_to_id(pWeapon);
		}
	}

	return 0;
}

AMX_NATIVE_INFO CstrikeNatives[] =
{
	{"cs_set_user_money",			cs_set_user_money},
	{"cs_get_user_money",			cs_get_user_money},
	{"cs_get_user_deaths",			cs_get_user_deaths},
	{"cs_set_user_deaths",			cs_set_user_deaths},
	{"cs_get_hostage_id",			cs_get_hostage_id},
	{"cs_get_weapon_silen",			cs_get_weapon_silenced},
	{"cs_set_weapon_silen",			cs_set_weapon_silenced},
	{"cs_get_weapon_burst",			cs_get_weapon_burstmode},
	{"cs_set_weapon_burst",			cs_set_weapon_burstmode},
	{"cs_get_user_vip",				cs_get_user_vip},
	{"cs_set_user_vip",				cs_set_user_vip},
	{"cs_get_user_team",			cs_get_user_team},
	{"cs_set_user_team",			cs_set_user_team},
	{"cs_get_user_buyzone",			cs_get_user_inside_buyzone},
	{"cs_get_user_mapzones",		cs_get_user_mapzones},
	{"cs_get_user_plant",			cs_get_user_plant},
	{"cs_set_user_plant",			cs_set_user_plant},
	{"cs_get_user_defuse",			cs_get_user_defusekit},
	{"cs_set_user_defuse",			cs_set_user_defusekit},
	{"cs_get_user_bpammo",			cs_get_user_backpackammo},
	{"cs_set_user_bpammo",			cs_set_user_backpackammo},
	{"cs_get_user_nvg",				cs_get_user_nvg},
	{"cs_set_user_nvg",				cs_set_user_nvg},
	{"cs_get_hostage_foll",			cs_get_hostage_follow},
	{"cs_set_hostage_foll",			cs_set_hostage_follow},
	{"cs_get_user_model",			cs_get_user_model},
	{"cs_set_user_model",			cs_set_user_model},
	{"cs_reset_user_model",			cs_reset_user_model},
	{"cs_set_weapon_ammo",			cs_set_weapon_ammo},
	{"cs_get_weapon_ammo",			cs_get_weapon_ammo},
	{"cs_get_user_hasprim",			cs_get_user_hasprimary},
	{"cs_get_no_knives",			cs_get_no_knives},
	{"cs_set_no_knives",			cs_set_no_knives},
	{"cs_get_weapon_id",			cs_get_weapon_id},
	{"cs_get_user_tked",			cs_get_user_tked},
	{"cs_set_user_tked",			cs_set_user_tked},
	{"cs_get_user_driving",			cs_get_user_driving},
	{"cs_get_user_stationary",		cs_get_user_stationary},
	{"cs_get_user_armor",			cs_get_user_armor},
	{"cs_set_user_armor",			cs_set_user_armor},
	{"cs_get_user_shield",			cs_get_user_shield},
	{"cs_user_spawn",				cs_user_spawn},
	{"cs_get_armoury_type",			cs_get_armoury_type},
	{"cs_set_armoury_type",			cs_set_armoury_type},
	{"cs_get_user_zoom",			cs_get_user_zoom},
	{"cs_set_user_zoom",			cs_set_user_zoom},
	{"cs_get_user_submodel",		cs_get_user_submodel},
	{"cs_set_user_submodel",		cs_set_user_submodel},
	{"cs_get_user_lastactivity",	cs_get_user_lastactivity},
	{"cs_set_user_lastactivity",	cs_set_user_lastactivity},
	{"cs_get_user_hostagekills",	cs_get_user_hostagekills},
	{"cs_set_user_hostagekills",	cs_set_user_hostagekills},
	{"cs_get_hostage_lastuse",		cs_get_hostage_lastuse},
	{"cs_set_hostage_lastuse",		cs_set_hostage_lastuse},
	{"cs_get_hostage_nextuse",		cs_get_hostage_nextuse},
	{"cs_set_hostage_nextuse",		cs_set_hostage_nextuse},
	{"cs_get_c4_explode_time",		cs_get_c4_explode_time},
	{"cs_set_c4_explode_time",		cs_set_c4_explode_time},
	{"cs_get_c4_defusing",			cs_get_c4_defusing},
	{"cs_set_c4_defusing",			cs_set_c4_defusing},
	{"cs_create_entity",			cs_create_entity },
	{"cs_find_ent_by_class",		cs_find_ent_by_class},
	{"cs_find_ent_by_owner",        cs_find_ent_by_owner},
	{"cs_set_ent_class",			cs_set_ent_class },
	{"cs_get_item_id",		        cs_get_item_id},
	{"cs_get_item_alias",			cs_get_item_alias},
	{"cs_get_translated_item_alias",cs_get_translated_item_alias},
	{"cs_get_weapon_info",          cs_get_weapon_info},
	{"cs_get_user_weapon_entity",   cs_get_user_weapon_entity},
	{"cs_get_user_weapon",          cs_get_user_weapon},
	{"cs_get_weaponbox_item",		cs_get_weaponbox_item},
	{nullptr,						nullptr}
};
