// 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

//
// Fun Module
//

#include "fun.h"
#include <HLTypeConversion.h>

HLTypeConversion TypeConversion;
CPlayers Players;

// native get_client_listen(receiver, sender)
static cell AMX_NATIVE_CALL get_client_listening(AMX *amx, cell *params)
{
	enum args { arg_count, arg_receiver, arg_sender };

	CHECK_PLAYER(params[arg_receiver]);
	CHECK_PLAYER(params[arg_sender]);

	return GETCLIENTLISTENING(params[arg_receiver], params[arg_sender]);
}

// native set_client_listen(receiver, sender, listen)
static cell AMX_NATIVE_CALL set_client_listening(AMX *amx, cell *params)
{
	enum args { arg_count, arg_receiver, arg_sender, arg_listen };

	CHECK_PLAYER(params[arg_receiver]);
	CHECK_PLAYER(params[arg_sender]);

	return SETCLIENTLISTENING(params[arg_receiver], params[arg_sender], params[arg_listen]);
}

// native set_user_godmode(index, godmode = 0)
static cell AMX_NATIVE_CALL set_user_godmode(AMX *amx, cell *params)
{
	enum args { arg_count, arg_user, arg_godmode };

	CHECK_PLAYER(params[arg_user]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);

	pPlayer->v.takedamage = params[arg_godmode] != 0 ? DAMAGE_NO : DAMAGE_AIM;

	return 1;
}

// native get_user_godmode(index)
static cell AMX_NATIVE_CALL get_user_godmode(AMX *amx, cell *params)
{
	enum args { arg_count, arg_user };

	CHECK_PLAYER(params[arg_user]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);

	return pPlayer->v.takedamage == DAMAGE_NO;
}

// native give_item(index, const item[])
static cell AMX_NATIVE_CALL give_item(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_item };

	CHECK_PLAYER(params[arg_index]);

	auto itemLength = 0;
	const auto item = MF_GetAmxString(amx, params[arg_item], 1, &itemLength);

	if (!itemLength
		||(strncmp(item, "weapon_", 7) != 0
		&& strncmp(item, "ammo_", 5) != 0
		&& strncmp(item, "item_", 5) != 0
		&& strncmp(item, "tf_weapon_", 10) != 0))
	{
		return 0;
	}

	auto pEntity = CREATE_NAMED_ENTITY(ALLOC_STRING(item));

	if (FNullEnt(pEntity))
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Item \"%s\" failed to create", item);
		return 0;
	}

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pEntity->v.origin = pPlayer->v.origin;
	pEntity->v.spawnflags |= SF_NORESPAWN;

	MDLL_Spawn(pEntity);

	const auto oldSolid = pEntity->v.solid;

	MDLL_Touch(pEntity, pPlayer);

	if (pEntity->v.solid == oldSolid)
	{
		REMOVE_ENTITY(pEntity); // The function did not fail - we're just deleting the item
		return -1;
	}

	return TypeConversion.edict_to_id(pEntity);
}

// native spawn(index)
static cell AMX_NATIVE_CALL spawn(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	CHECK_ENTITY(params[arg_index]);

	const auto pEntity = TypeConversion.id_to_edict(params[arg_index]);

	MDLL_Spawn(pEntity);

	return 1;
}

// native set_user_health(index, health)
static cell AMX_NATIVE_CALL set_user_health(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_health };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
	const auto health  = float(params[arg_health]);

	if (health > 0.0f)
	{
		pPlayer->v.health = health;
	}
	else
	{
		MDLL_ClientKill(pPlayer);
	}

	return 1;
}

// native set_user_frags(index, frags)
static cell AMX_NATIVE_CALL set_user_frags(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_frags };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pPlayer->v.frags = float(params[arg_frags]);

	return 1;
}

// native set_user_armor(index, armor)
static cell AMX_NATIVE_CALL set_user_armor(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_armor };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pPlayer->v.armorvalue = float(params[arg_armor]);

	return 1;
}

// native set_user_origin(index, const origin[3])
static cell AMX_NATIVE_CALL set_user_origin(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_origin };

	CHECK_PLAYER(params[arg_index]);

	auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
	const auto pVector = MF_GetAmxAddr(amx, params[arg_origin]);

	SET_SIZE(pPlayer, pPlayer->v.mins, pPlayer->v.maxs);
	SET_ORIGIN(pPlayer, Vector(float(pVector[0]), float(pVector[1]), float(pVector[2])));

	return 1;
}

// native set_user_rendering(index, fx = kRenderFxNone, r = 0, g = 0, b = 0, render = kRenderNormal, amount = 0)
static cell AMX_NATIVE_CALL set_user_rendering(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };

	CHECK_PLAYER(params[arg_index]);

	auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pPlayer->v.renderfx    = params[arg_fx];
	pPlayer->v.rendercolor = Vector(float(params[arg_red]), float(params[arg_green]), float(params[arg_blue]));
	pPlayer->v.rendermode  = params[arg_render];
	pPlayer->v.renderamt   = float(params[arg_amount]);

	return 1;
}

// get_user_rendering(index, &fx = kRenderFxNone, &r = 0, &g = 0, &b = 0, &render = kRenderNormal, &amount = 0);
static cell AMX_NATIVE_CALL get_user_rendering(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };

	CHECK_PLAYER(params[arg_index]);

	auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	*MF_GetAmxAddr(amx, params[arg_fx])     = pPlayer->v.renderfx;
	*MF_GetAmxAddr(amx, params[arg_red])    = pPlayer->v.rendercolor[0];
	*MF_GetAmxAddr(amx, params[arg_green])  = pPlayer->v.rendercolor[1];
	*MF_GetAmxAddr(amx, params[arg_blue])   = pPlayer->v.rendercolor[2];
	*MF_GetAmxAddr(amx, params[arg_render]) = pPlayer->v.rendermode;
	*MF_GetAmxAddr(amx, params[arg_amount]) = pPlayer->v.renderamt;

	return 1;
}

// native set_user_maxspeed(index, Float:speed = -1.0)
static cell AMX_NATIVE_CALL set_user_maxspeed(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_speed };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer  = TypeConversion.id_to_edict(params[arg_index]);
	const auto newSpeed = amx_ctof(params[arg_speed]);

	SETCLIENTMAXSPEED(pPlayer, newSpeed);
	pPlayer->v.maxspeed = newSpeed;

	return 1;
}

// native Float:get_user_maxspeed(index)
static cell AMX_NATIVE_CALL get_user_maxspeed(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	return amx_ftoc(pPlayer->v.maxspeed);
}

// native set_user_gravity(index, Float:gravity = 1.0)
static cell AMX_NATIVE_CALL set_user_gravity(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_gravity };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pPlayer->v.gravity = amx_ctof(params[arg_gravity]);

	return 1;
}

// native Float:get_user_gravity(index)
static cell AMX_NATIVE_CALL get_user_gravity(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	return amx_ftoc(pPlayer->v.gravity);
}

// native set_user_hitzones(index = 0, target = 0, body = HITZONES_DEFAULT)
static cell AMX_NATIVE_CALL set_user_hitzones(AMX *amx, cell *params)
{
	enum args { arg_count, arg_attacker, arg_target, arg_hitzones };

	const int attacker = params[arg_attacker];
	const int target   = params[arg_target];
	const int hitzones = params[arg_hitzones];

	if (attacker == 0 && target == 0)
	{
		Players.SetEveryoneBodyHits(hitzones);
	}
	else if (attacker == 0 && target != 0)
	{
		CHECK_PLAYER(target);

		Players.SetAttackersBodyHits(target, hitzones);
	}
	else if (attacker != 0 && target == 0)
	{
		CHECK_PLAYER(attacker);

		Players.SetTargetsBodyHits(attacker, hitzones);
	}
	else
	{
		CHECK_PLAYER(attacker);
		CHECK_PLAYER(target);

		Players.SetBodyHits(attacker, target, hitzones);
	}

	return 1;
}

// native get_user_hitzones(index, target)
static cell AMX_NATIVE_CALL get_user_hitzones(AMX *amx, cell *params)
{
	enum args { arg_count, arg_attacker, arg_target };

	const auto attacker = params[arg_attacker];

	CHECK_PLAYER(attacker);

	const auto target = params[arg_target];

	CHECK_PLAYER(target);

	return Players[attacker].GetBodyHits(target);
}

// native set_user_noclip(index, noclip = 0)
static cell AMX_NATIVE_CALL set_user_noclip(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_noclip };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	pPlayer->v.movetype = params[arg_noclip] != 0 ? MOVETYPE_NOCLIP : MOVETYPE_WALK;

	return 1;
}

// native get_user_noclip(index)
static cell AMX_NATIVE_CALL get_user_noclip(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	CHECK_PLAYER(params[arg_index]);

	const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);

	return pPlayer->v.movetype == MOVETYPE_NOCLIP;
}

// native set_user_footsteps(id, set = 1)
static cell AMX_NATIVE_CALL set_user_footsteps(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index, arg_footsteps };

	const auto index = params[arg_index];

	CHECK_PLAYER(index);

	const auto pPlayer = TypeConversion.id_to_edict(index);

	if (params[arg_footsteps] != 0)
	{
		pPlayer->v.flTimeStepSound = 999;
		Players[index].SetSilentFootsteps(true);

		g_pFunctionTable->pfnPlayerPreThink = PlayerPreThink;
	}
	else
	{
		pPlayer->v.flTimeStepSound = STANDARDTIMESTEPSOUND;
		Players[index].SetSilentFootsteps(false);

		if (g_pFunctionTable->pfnPlayerPreThink && !Players.HaveSilentFootsteps())
		{
			g_pFunctionTable->pfnPlayerPreThink = nullptr;
		}
	}

	return 1;
}

// native get_user_footsteps(index)
static cell AMX_NATIVE_CALL get_user_footsteps(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	const auto index = params[arg_index];

	CHECK_PLAYER(index);

	return Players[index].HasSilentFootsteps();
}

// native strip_user_weapons(index)
static cell AMX_NATIVE_CALL strip_user_weapons(AMX *amx, cell *params)
{
	enum args { arg_count, arg_index };

	const auto index = params[arg_index];

	CHECK_PLAYER(index);

	const auto pPlayer = TypeConversion.id_to_edict(index);
	const auto pEntity = CREATE_NAMED_ENTITY(MAKE_STRING("player_weaponstrip"));

	if (FNullEnt(pEntity))
	{
		return 0;
	}

	MDLL_Spawn(pEntity);
	MDLL_Use(pEntity, pPlayer);
	REMOVE_ENTITY(pEntity);

	*reinterpret_cast<int *>(MF_PlayerPropAddr(index, Player_CurrentWeapon)) = 0;

	return 1;
}


AMX_NATIVE_INFO fun_Exports[] =
{
	{ "get_client_listen" , get_client_listening },
	{ "set_client_listen" , set_client_listening },
	{ "set_user_godmode"  , set_user_godmode     },
	{ "get_user_godmode"  , get_user_godmode     },
	{ "set_user_health"   , set_user_health      },
	{ "give_item"         , give_item            },
	{ "spawn"             , spawn                },
	{ "set_user_frags"    , set_user_frags       },
	{ "set_user_armor"    , set_user_armor       },
	{ "set_user_origin"   , set_user_origin      },
	{ "set_user_rendering", set_user_rendering   },
	{ "get_user_rendering",	get_user_rendering   },
	{ "set_user_maxspeed" , set_user_maxspeed    },
	{ "get_user_maxspeed" , get_user_maxspeed    },
	{ "set_user_gravity"  , set_user_gravity     },
	{ "get_user_gravity"  , get_user_gravity     },
	{ "get_user_footsteps", get_user_footsteps   },
	{ "set_user_hitzones" , set_user_hitzones    },
	{ "get_user_hitzones" , get_user_hitzones    },
	{ "set_user_noclip"   , set_user_noclip      },
	{ "get_user_noclip"   , get_user_noclip      },
	{ "set_user_footsteps", set_user_footsteps   },
	{ "strip_user_weapons", strip_user_weapons   },
	{ nullptr             , nullptr              }
};


void PlayerPreThink(edict_t *pEntity)
{
	const auto index = TypeConversion.edict_to_id(pEntity);

	if (Players[index].HasSilentFootsteps())
	{
		pEntity->v.flTimeStepSound = 999;
		RETURN_META(MRES_HANDLED);
	}

	RETURN_META(MRES_IGNORED);
}

int ClientConnect(edict_t *pPlayer, const char *pszName, const char *pszAddress, char szRejectReason[128])
{
	const auto index = TypeConversion.edict_to_id(pPlayer);

	Players[index].Clear();

	RETURN_META_VALUE(MRES_IGNORED, 0);
}

void TraceLine(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr)
{
	TRACE_LINE(v1, v2, fNoMonsters, shooter, ptr);

	if (ptr->pHit && (ptr->pHit->v.flags & (FL_CLIENT | FL_FAKECLIENT))
	    && shooter &&  (shooter->v.flags & (FL_CLIENT | FL_FAKECLIENT)) )
	{
		const auto shooterIndex = TypeConversion.edict_to_id(shooter);
		const auto targetIndex  = TypeConversion.edict_to_id(ptr->pHit);

		if (!(Players[shooterIndex].GetBodyHits(targetIndex) & (1 << ptr->iHitgroup)))
		{
			ptr->flFraction = 1.0;
		}
	}

	RETURN_META(MRES_SUPERCEDE);
}


void OnAmxxAttach()
{
	MF_AddNatives(fun_Exports);
}

void OnPluginsLoaded()
{
	Players.Clear();

	TypeConversion.init();

	g_pFunctionTable->pfnPlayerPreThink = nullptr;
	g_pengfuncsTable_Post->pfnTraceLine = nullptr;
}

void ServerDeactivate()
{
	g_pFunctionTable->pfnPlayerPreThink = nullptr;
	g_pengfuncsTable_Post->pfnTraceLine = nullptr;

	RETURN_META(MRES_IGNORED);
}