#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
#include <cstrike>
#include <respawn>

/* Most of these idea are from CSDM 2.1 by BAILOPAN. When the player select team, model, Weapon Removal
 * Also the idea of when the player actually spawned. The reason I took this into a plugin
 * instead of just using the module CSDM was that I was only going to use it for my kz plugin.
 *
 * Experimental version.
 * I found that with respawn time of 3 secs with speeding up the death animation by 2x. That there
 * are no more ghost/dead body models on the floor. Seem to work very well.
 */

#pragma semicolon 1
#define RC_VERSION "0.04"

// Forwards
new fwd_RoundStart;
new fwd_RoundEnd;
new fwd_DeathMessage;
new fwd_PostSpawn;
new fwd_PreSpawn;
new fwd_result;

new OFFSET_INTERNALMODEL;

// Pointer Cvar
new spawn_time;
new weapon_stay;

// If round ended. Don't respawn player from death.
new bool:RoundEnded;

new bool:spawned[33];
new bool:spawning[33];
new bool:joined[33];
new Float:wait[33];

new MaxPlayers;
new respawn_enabled;

public plugin_init()
{
	register_plugin("Respawn Controller", RC_VERSION, "teame06");

	// Fakemeta Forwards
	register_forward(FM_AlertMessage, "fm_AlertMessage", 1);
	register_forward(FM_PlayerPreThink, "fm_PostThink");
	register_forward(FM_SetModel, "forward_set_model");

	// Events
	register_event("DeathMsg", "Event_DeathMsg", "a");
	register_event("ShowMenu", "menuclass", "b", "4&CT_Select", "4&Terrorist_Select");
	register_event("VGUIMenu", "menuclass", "b", "1=26", "1=27");

	// Other Forwards
	fwd_RoundStart = CreateMultiForward("RoundStart", ET_STOP);
	fwd_RoundEnd = CreateMultiForward("RoundEnd", ET_IGNORE);
	fwd_DeathMessage = CreateMultiForward("DeathMessage", ET_IGNORE, FP_CELL, FP_CELL, FP_CELL, FP_STRING);
	fwd_PreSpawn = CreateMultiForward("PreSpawn", ET_IGNORE, FP_CELL);
	fwd_PostSpawn = CreateMultiForward("PostSpawn", ET_IGNORE, FP_CELL);

	// Cvars
	spawn_time = register_cvar("respawn_time", "3.0");
	respawn_enabled = register_cvar("respawn_enabled", "0", FCVAR_SERVER|FCVAR_SPONLY);
	register_cvar("respawn_version", RC_VERSION, FCVAR_SERVER|FCVAR_SPONLY);
	weapon_stay = register_cvar("weapon_stay", "5");
	MaxPlayers = get_maxplayers();

	OFFSET_INTERNALMODEL = is_amd64_server() ? 152 : 126;

	// Weapon Removal
	register_clcmd("drop", "hook_drop");

	new config[64];
	get_configsdir(config, 63);
	server_cmd("exec %s/respawn.cfg", config);
	server_exec();
}

public plugin_natives()
{
	register_library("respawn");
	register_native("spawn_player", "native_spawn");
}

public native_spawn(id, nums)
{
	if(nums != 1)
		return log_error(10, "Bad native parameters");

	if(get_pcvar_num(respawn_enabled))
	{
		new index = get_param(1);

		if(index > 0 && index <= MaxPlayers)
		{
			if(is_user_connected(index))
			{
				spawning[index] = true;
				spawned[index] = false;
				respawn(index, 1);
			}
			else
				return log_error(10, "User is not connected"); // Need to change this.
		}
		else
			return log_error(10, "User ID out of range");
	}

	return 0;
}

public fm_AlertMessage(at_type, const message[])
{
	if(get_pcvar_num(respawn_enabled) && at_type == 5)
	{
		if(equal(message[17], "Round_Start", 11))
		{
			RoundEnded = false;
			ExecuteForward(fwd_RoundStart, fwd_result);
		}
		else if(equal(message[17], "Round_End", 9))
		{
			RoundEnded = true;
			ExecuteForward(fwd_RoundEnd, fwd_result);
		}
	}
	return FMRES_IGNORED;
}

public fm_PostThink(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return FMRES_IGNORED;

	if(!is_user_connected(id))
		return FMRES_IGNORED;

	// Speed up death animation 2x
	if(pev(id, pev_deadflag) == DEAD_DYING)
		set_pev(id, pev_framerate, 2.0);

	if(spawned[id])
		return FMRES_IGNORED;

	static CsTeams:team;
	team = cs_get_user_team(id);

	if(team != CS_TEAM_CT && team != CS_TEAM_T)
		return FMRES_IGNORED;

	if(GetPlayerModel(id) == 0xFF)
		return FMRES_IGNORED;

	if(is_user_alive(id) && !(pev(id, pev_flags) & FL_SPECTATOR))
	{
		if(!joined[id])
			joined[id] = true;

		spawning[id] = false;
		spawned[id] = true;

		ExecuteForward(fwd_PostSpawn, fwd_result, id);
	}
	else if(!spawning[id])
	{
		if(wait[id] == -1.0)
		{
			spawning[id] = true;
			respawn(id, 0);
			wait[id] = 0.0;
		}
		if(wait[id] < 0.1)
			wait[id] = _time();
		else if(_time() - wait[id] > FRAMEWAIT)
		{
			spawning[id] = true;
			respawn(id);
		}
	}
	return FMRES_IGNORED;
}

Float:_time()
{
	new Float:engine_time;
	global_get(glb_time, engine_time);
	return engine_time;
}

public Event_DeathMsg()
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	new Victim = read_data(2);

	if(!Victim)
		return;

	static weaponname[32];
	new CsTeams:team = cs_get_user_team(Victim);
	read_data(4, weaponname, 31);

	spawned[Victim] = false;

	// Would forwarding a weapon id would be better? I know it doesn't have weapon_ in the message.
	// Due to if any uses a certain natives in 1.75a and below will replace weaponname string with something else.
	ExecuteForward(fwd_DeathMessage, fwd_result, read_data(1), Victim, read_data(3), weaponname);

	if(team == CS_TEAM_CT || team == CS_TEAM_T)
	{
		if(GetPlayerModel(Victim) != 0xFF)
		{
			spawning[Victim] = true;

			new Float:time = get_pcvar_float(spawn_time);

			set_task(time, "respawn_player", Victim);

			if(time >= 3.0)
				set_task(2.8, "freeze_player", Victim);
		}
	}

	static weapons[32], num, name[24];
	new wp, slot;

	get_user_weapons(Victim, weapons, num);

	for (new i=0; i<num; i++)
	{
		wp = weapons[i];
		slot = g_WeaponSlots[wp];

		if (slot == SLOT_PRIMARY || slot == SLOT_SECONDARY || slot == SLOT_C4)
		{
			get_weaponname(wp, name, 23);

			if(get_pcvar_float(weapon_stay) != -1)
				set_task(get_pcvar_float(weapon_stay), "delay", Victim, name, 24);
		}
	}
}

public freeze_player(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	if(!is_user_connected(id))
		return;

	new CsTeams:team = cs_get_user_team(id);
	if(team == CS_TEAM_CT || team == CS_TEAM_T)
	{
		if(GetPlayerModel(id) != 0xFF)
		{
			set_pev(id, pev_flags, pev(id, pev_flags) | FL_FROZEN);
			set_pev(id, pev_deadflag, DEAD_DISCARDBODY);
		}
	}
}

public respawn_player(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	if(!is_user_connected(id))
		return;

	new CsTeams:team = cs_get_user_team(id);
	if(team == CS_TEAM_CT || team == CS_TEAM_T)
	{
		if(GetPlayerModel(id) != 0xFF)
		{
			respawn(id, 0);
		}
	}
}

respawn(id, flags = 1)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	if(RoundEnded) // If round ended. Don't respawn player.
		return;

	ExecuteForward(fwd_PreSpawn, fwd_result, id);

	if(fwd_result > 0)
		return;

	if(flags == 0)
	{
		if(is_user_alive(id))
			return;

		// If just use with only DEAD_RESPAWNABLE
		// There are times where it doesn't respawn the player.
		// So they will have to type /respawn to respawn.
		set_pev(id, pev_deadflag, DEAD_RESPAWNABLE);

		// DLLFunc_Spawn would sometime spawn a
		// player without a hud from DeathMsg.
		//dllfunc(DLLFunc_Spawn, id);
		dllfunc(DLLFunc_Think, id);
	}
	else
	{
		dllfunc(DLLFunc_Spawn, id);
	}
}

public menuclass(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	// They changed teams
	SetPlayerModel(id, 0xFF);

	if(joined[id])
		wait[id] = -1.0;
}

public RoundStart()
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	for(new i = 0; i <= MaxPlayers; i++)
		spawned[i] = false;
}

GetPlayerModel(id)
{
	if(!is_user_connected(id))
		return 0;

	return get_pdata_int(id, OFFSET_INTERNALMODEL, 5);
}

SetPlayerModel(id, int)
{
	if(!is_user_connected(id))
		return;

	set_pdata_int(id, OFFSET_INTERNALMODEL, int, 5);
}

public client_connect(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	spawned[id] = false;
	spawning[id] = false;
	joined[id] = false;
	wait[id] = 0.0;
}

public hook_drop(id)
{
	if(!get_pcvar_num(respawn_enabled))
		return;

	static wp, c, a, name[24];
	if (read_argc() <= 1)
	{
		wp = get_user_weapon(id, c, a);
	} else {
		read_argv(1, name, 23);
		wp = get_weaponid(name);
	}

	if (wp)
	{
		static name[24];
		get_weaponname(wp, name, 23);

		if(get_pcvar_float(weapon_stay) != -1)
			set_task(get_pcvar_float(weapon_stay), "delay", id, name, 24);
	}
}

public delay(name[], player)
{
	remove_weapon(player, name);
}

remove_weapon(player, const weapon[])
{
	// Check index.
	if (player < 1 || player > MaxPlayers)
	{
		log_amx("Invalid player %d", player);
		return;
	}

	static ent, ent2;
	ent = engfunc(EngFunc_FindEntityByString, -1, "classname", "weaponbox");

	while(ent > 0)
	{
		if(pev_valid(ent))
		{
			if (pev(ent, pev_owner) == player)
			{
				ent2 = engfunc(EngFunc_FindEntityByString, -1, "classname", weapon);

				while(ent2 > 0 && pev_valid(ent2))
				{
					if (pev(ent2, pev_owner) == ent)
					{
						engfunc(EngFunc_RemoveEntity, ent2);
						engfunc(EngFunc_RemoveEntity, ent);
						return;
					}
					ent2 = engfunc(EngFunc_FindEntityByString, ent2, "classname", weapon);
				}
			}
		}
		ent = engfunc(EngFunc_FindEntityByString, ent, "classname", "weaponbox");
	}
}