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

// comment to avoid autodisabling the plugin on maps which not contain bomb targets
#define BOMB_MAP_CHECK

// float value, hud messages display time (in seconds)
#define MSG_TIME 7.0

// do not set this value less than "maxplayers"
#define MAX_PLAYERS 32

// initial AMXX version number supported CVAR pointers in get/set_pcvar_* natives
#define CVAR_POINTERS_AMXX_INIT_VER_NUM 170

// determine if get/set_pcvar_* natives can be used
#if defined AMXX_VERSION_NUM && AMXX_VERSION_NUM >= CVAR_POINTERS_AMXX_INIT_VER_NUM
	#define CVAR_POINTERS
	new g_pcvar_spawn
	new g_pcvar_time
#endif

new TEAM[] = "TERRORIST"
new WEAPON[] = "weapon_c4"

#define	FL_ONGROUND (1<<9)

new bool:g_freezetime = true
new bool:g_spawn
new bool:g_planting

new g_carrier

new g_pos[MAX_PLAYERS + 1][3]
new g_time[MAX_PLAYERS + 1]

new g_maxplayers

// Do not touch the following items:
#define MENU_SIZE 256

new AfkT
new AfkCT
new numTalive
new numCTalive
new bombcarrier
new lastCounterShow
new i_afktime[33]
new i_spectime[33]
new s_plname[33][33]
new f_lastangles[33][3]
new f_spawnposition[33][3]
new bool:b_spawned[33] = {true, ...}
new bool:b_demorec[33] = {false, ...}
new bool:b_afkname[33] = {false, ...}
new bool:is_admin[33] = {false, ...}
new bool:b_tobeslayed[33] = {false, ...}

public plugin_init()
{
	register_plugin("AFK KiSSS","3.0.0 Beta 2","ZmifF")
	
	register_dictionary("afkkicker.txt")
	register_dictionary("common.txt")

	register_cvar("afk_check_freq", "15") 		// Check for afk-frequency. This is also the warning message frequency.
	register_cvar("spec_check_freq", "60") 		// Check for spec-kick. Has effect in afk_options 2 mode only.
	register_cvar("bomb_drop_time", "15") 		// Time until bomb gets dropped, it is also afk recognition time.
	register_cvar("show_freq", "20") 		// Frequence of afk-count-messages, only needed if the CVAR afk_show_counter is set to 1
	register_cvar("warning_time", "60") 		// Time to start warning players before kick or spec-switch
	
	register_cvar("afk_slaytime", "30") 		// AFK-Time until slay
	register_cvar("afk_optstime", "90") 		// AFK-Time until afk_options will take effect (afk_kicktime in v.2)
	register_cvar("afk_options", "2") 		// 0 Spec, 1 Kick, 2 Spec+Kick, 3 Kick normal Players and Switch Admins to Spec, 4 nothing of these in case 3 afk_adminkimmunity will have no effect
	register_cvar("afk_speckick", "60") 		// time to be spec until kick (afk_options 2 only)
	register_cvar("afk_minplayers", "6") 		// Minimum players to kick someone (afk_options 1 and 2 only)

	// 0 deactivate, 1 activate the following functions
	register_cvar("afk_bombdrop", "0") 		// 1 Bomb will be dropped after BOMB_DROP_TIME
	register_cvar("afk_slayplayers", "1") 		// 1 Slays AFK-Players when last survivor
	register_cvar("afk_adminsimmunity", "0") 	// 1 Admin immune against slay
	register_cvar("afk_adminkimmunity", "1") 	// 1 Admin immune against kick (against spec-kick to) (afk_options 1 and 2 only)
	register_cvar("afk_show_counter", "0") 		// 1 Displays a message every 20 seconds with the numbers and teams of afk_players ...
	register_cvar("afk_hud_messages", "0") 		// 1 Use HUD Message with bomb transfer

	// ... if at least 1 AFK detected
	register_cvar("afk_disable", "0") // 1 Disable this plugin (4 example for clanwars)

	register_event("ResetHUD", "playerSpawned", "be")

	register_logevent("bomb_events", 3, "1=triggered", "2=Spawned_With_The_Bomb", "2=Dropped_The_Bomb", "2=Got_The_Bomb", "2=Planted_The_Bomb")

	set_task(float(get_cvar_num("afk_check_freq")),"checkPositions",_,_,_,"b")
	set_task(float(get_cvar_num("afk_check_freq")),"checkDeath",_,_,_,"b")

#if defined CVAR_POINTERS
	g_pcvar_spawn = register_cvar("afk_bombtransfer_spawn", "7")
	g_pcvar_time = register_cvar("afk_bombtransfer_time", "15")
#else
	register_cvar("afk_bombtransfer_spawn", "7")
	register_cvar("afk_bombtransfer_time", "15")
#endif

#if defined BOMB_MAP_CHECK
	// is current map not contain bomb targets?
	if (!engfunc(EngFunc_FindEntityByString, -1, "classname", "func_bomb_target"))
		return
#endif

	register_event("WeapPickup", "event_got_bomb", "be", "1=6")
	register_event("BarTime", "event_bar_time", "be")
	register_event("TextMsg", "event_bomb_drop", "bc", "2=#Game_bomb_drop")
	register_event("TextMsg", "event_bomb_drop", "a", "2=#Bomb_Planted")
	register_event("HLTV", "event_new_round", "a", "1=0", "2=0")

	register_logevent("logevent_round_start", 2, "1=Round_Start")

	set_task(1.0, "task_afk_check", _, _, _, "b") // plugin's core loop

	g_maxplayers = get_maxplayers()
}

public checkPositions()
{
	new playernum, pl, t_slay, t_opts, t_bomb, t_slay_time, t_opts_time, min_players
	new a_ids[32], playerpos[3], playerview[3]
	if(get_cvar_num("afk_disable")) return PLUGIN_HANDLED
	get_players(a_ids, playernum, "ac")
	t_slay = get_cvar_num("afk_slayplayers")
	t_opts = get_cvar_num("afk_options")
	t_bomb = get_cvar_num("afk_bombdrop")
	t_slay_time = get_cvar_num("afk_slaytime")
	t_opts_time = get_cvar_num("afk_optstime")
	min_players = get_cvar_num("afk_minplayers")

	if(t_opts == 2)
	{
		if(!(task_exists(29034,0)))
			set_task(float(get_cvar_num("spec_check_freq")),"checkSpec",29034,_,_,"b")
	}

	get_alive_nums()
	for(new i = 0; i < playernum; i++) {
		pl = a_ids[i]
		if(is_user_connected(pl) && !is_user_bot(pl) && !is_user_hltv(pl) && is_user_alive(pl) && b_spawned[pl])
		{
			get_user_origin(pl, playerview, 3)
			get_user_origin(pl, playerpos)

			// Has player moved since last check?
			if((playerview[0] == f_lastangles[pl][0] && playerview[1] == f_lastangles[pl][1] && playerview[2] == f_lastangles[pl][2]) || (playerpos[0] == f_spawnposition[pl][0] && playerpos[1] == f_spawnposition[pl][1] && playerpos[2] == f_spawnposition[pl][2]))
			{
				i_afktime[pl] += get_cvar_num("afk_check_freq")
				if(t_bomb == 1 && i_afktime[pl] >= get_cvar_num("bomb_drop_time") && pl == bombcarrier)
				{
					client_cmd(pl,"use weapon_c4")
					client_cmd(pl,"drop")
					client_print(0, print_chat, "[AFK]: %L", LANG_PLAYER, "AFK_BOMB_DROP")
				}
				if(t_opts == 0 || t_opts == 2)
				{
					if(playernum >= min_players)
						CheckSwitchSpec(pl, t_opts_time)
				}
				if(t_opts == 1 || t_opts == 3)
				{
					if(playernum >= min_players)
						checkKick(pl, t_opts, t_opts_time)
				}
				if(t_slay == 1)
				{
					if(t_slay_time <= i_afktime[pl])
						checkSlay(pl)
				}
			} else {
				i_afktime[pl] = 0
			}
			f_lastangles[pl][0] = playerview[0]
			f_lastangles[pl][1] = playerview[1]
			f_lastangles[pl][2] = playerview[2]
		}
	}
	afk_rs_msg()
	if((numTalive == 0 && AfkT > 0) || (numCTalive == 0 && AfkCT > 0))
		client_print(0, print_chat, "[AFK]: %L", LANG_PLAYER, "AFK_INFO_MSG")
	
	return PLUGIN_HANDLED
}

// Handle Situations

//Check for Slay
checkSlay(id)
{
	if(!((cs_get_user_team(id) == CS_TEAM_T && numTalive > 0) || (cs_get_user_team(id) == CS_TEAM_CT && numCTalive > 0)))
	{
		if(!(get_playersnum() < get_cvar_num("afk_minplayers") || (get_cvar_num("afk_adminsimmunity") == 1 && is_admin[id])))
		{
			client_cmd(id,"kill")
			b_tobeslayed[id] = true
		}
	}
}

CheckSwitchSpec(id, opts_time)
{
	if (opts_time-get_cvar_num("warning_time") <= i_afktime[id] < opts_time)
	{
		new timeleft = opts_time - i_afktime[id]
		client_print(id, print_chat, "[AFK]: %L", id, "AFK_STIME", timeleft)
	} else if (i_afktime[id] > opts_time) {
		SwitchSpec(id)
	}
	
	return PLUGIN_CONTINUE
}

public checkKick(id, opt, opts_time)
{
	if(get_cvar_num("afk_adminsimmunity") == 1 && is_admin[id] && opt == 1)
	{
		return PLUGIN_HANDLED
	} else {
		if(opts_time-get_cvar_num("warning_time") <= i_afktime[id] < opts_time)
		{
			new timeleft = opts_time - i_afktime[id]
			if(is_admin[id] && opt == 3)
			{
				client_print(id, print_chat, "[AFK]: %L", id, "AFK_STIME", timeleft)
			} else {
				client_print(id, print_chat, "[AFK]: %L", id, "AFK_TIMELEFT", timeleft)
			}
		} else if (i_afktime[id] > opts_time) {
			if(is_admin[id] && opt == 3)
			{
				SwitchSpec(id)
			} else {
				new name[33]
				get_user_name(id, name, 32)
				client_print(0, print_chat, "[AFK]: %L", LANG_PLAYER, "AFK_KICKED", name, opts_time)
				server_cmd("kick #%d ^"%L^"", get_user_userid(id), id, "AFK_YOU_KICKED", opts_time)
				log_amx("%s was kicked for being AFK longer than %i seconds", name, opts_time)
			}
		}
	}
	
	return PLUGIN_CONTINUE
}

SwitchSpec(id)
{
	client_cmd(id,"kill")
	cs_set_user_team(id, CS_TEAM_UNASSIGNED)
	client_print(0, print_chat, "[AFK]: %L", id, "AFK_SPEC_SWITCH")
	b_tobeslayed[id] = true
	i_spectime[id] = 0
	i_afktime[id] = 0
	
	return PLUGIN_CONTINUE
}

// Control Spec-Players
public checkSpec()
{
	new playernum, admin_imun, pl, kicktime, j
	new a_ids[32]
	admin_imun = get_cvar_num("afk_adminkimmunity")
	kicktime = get_cvar_num("afk_speckick")
	get_players(a_ids,playernum,"ce","UNASSIGNED")
	for(j = 0; j < playernum; j++) {
		pl = a_ids[j]
		if(!is_user_hltv(pl) && is_user_connected(pl) && !is_user_bot(pl))
		{
			if(!(admin_imun == 1 && is_admin[pl]))
			{
				i_spectime[pl] += get_cvar_num("spec_check_freq")
				if(i_spectime[pl] > kicktime)
				{
					new name[33]
					get_user_name(pl, name, 32)
					client_print(0, print_chat, "[AFK]: %L", LANG_PLAYER, "AFK_KICKED", name, kicktime)
					server_cmd("kick #%d ^"%L^"", get_user_userid(pl), pl, "AFK_YOU_KICKED", kicktime)
					log_amx("%s was kicked for being AFK longer than %i seconds", name, kicktime)
				}
			}
		}
	}
	
	return PLUGIN_HANDLED
}

// Help functions

// Verifies if players are really dead
public checkDeath()
{
	new playernum, pl
	new a_ids[32]

	if(get_cvar_num("afk_disable")) return PLUGIN_HANDLED
	get_players(a_ids, playernum, "ac")

	for(new i = 0; i < playernum; i++)
	{
		pl = a_ids[i]
		if(b_tobeslayed[pl])
			client_cmd(pl,"kill")
	}
	
	return PLUGIN_HANDLED
}

// Tracks the bombholder
public bomb_events()
{
	new arg0[64], action[64], name[33], userid, bid

	if(get_cvar_num("afk_disable")) return PLUGIN_HANDLED

	// Read the log data that we need 
	read_logargv(0,arg0,63) 
	read_logargv(2,action,63) 

	// Find the id of the player that triggered the log 
	parse_loguser(arg0,name,32,userid) 
	bid = find_player("k",userid) 

	// Find out what action it was 
	if (equal(action,"Spawned_With_The_Bomb"))
	{ 
		bombcarrier = bid; 
	} else if (equal(action,"Dropped_The_Bomb")) {
		bombcarrier = 0; 
	} else if (equal(action,"Got_The_Bomb")) {
		bombcarrier = bid; 
	} else if (equal(action, "Planted_The_Bomb")) {
	bombcarrier = 0; 
	}
	
	return PLUGIN_HANDLED
}

// Collect and displays informations about number and team of afk-players
public afk_rs_msg()
{
	new playerCount, i, player
	new Players[32] 
	get_players(Players, playerCount, "ac")
	AfkT = 0
	AfkCT = 0 

	for (i=0; i<playerCount; i++)
	{
		player = Players[i]
		if(i_afktime[player] > get_cvar_num("bomb_drop_time"))
		{
			if(cs_get_user_team(player) == CS_TEAM_T)
			AfkT++
			if(cs_get_user_team(player) == CS_TEAM_CT)
			AfkCT++
		}
	}
	if((AfkT > 0 || AfkCT > 0) && get_cvar_num("afk_show_counter") == 1)
	{
		lastCounterShow += get_cvar_num("afk_check_freq")
		if(lastCounterShow >= get_cvar_num("show_freq"))
		{
			client_print(0, print_chat, "[AFK]: %i T AFK - %i CT AFK", AfkT, AfkCT)
			lastCounterShow = 0
		}
	}
	
	return PLUGIN_CONTINUE
}

// Retrieves the number of non-afk alive players
get_alive_nums()
{
	new playerCount, i, gplayer
	new Players[32] 
	get_players(Players, playerCount, "ac")
	numCTalive = 0
	numTalive = 0
	
	for (i=0; i<playerCount; i++)
	{
		gplayer = Players[i]
		if(cs_get_user_team(gplayer) == CS_TEAM_T && i_afktime[gplayer] < get_cvar_num("bomb_drop_time"))
		numTalive++
		if(cs_get_user_team(gplayer) == CS_TEAM_CT && i_afktime[gplayer] < get_cvar_num("bomb_drop_time"))
		numCTalive++
	}
	
	return PLUGIN_CONTINUE
}

// On new Round get Spawnpositions
public playerSpawned(id)
{
	b_spawned[id] = false
	b_demorec[id] = false
	b_tobeslayed[id] = false
	new a_id[1]
	a_id[0] = id
	set_task(0.75, "getFirstPos",_, a_id, 1)
	
	return PLUGIN_HANDLED
}

public getFirstPos(a_id[])
{
	new id = a_id[0]
	b_spawned[id] = true
	
	get_user_origin(id, f_lastangles[id], 3)
	get_user_origin(id, f_spawnposition[id])
	
	if(get_user_flags(id)&ADMIN_IMMUNITY)
		is_admin[id] = true
	
	return PLUGIN_HANDLED
}

// On player connect

public client_connect(id)
{
	i_afktime[id] = 0
	i_spectime[id] = 0
	b_spawned[id] = false
	b_demorec[id] = false
	b_afkname[id] = false
	is_admin[id] = false
	b_tobeslayed[id] = false
	get_user_name(id, s_plname[id], 32)
	
	return PLUGIN_HANDLED
}

public client_putinserver(id)
{
	i_afktime[id] = 0
	i_spectime[id] = 0
	b_spawned[id] = false
	b_demorec[id] = false
	b_afkname[id] = false
	is_admin[id] = false
	b_tobeslayed[id] = false
	get_user_name(id, s_plname[id], 32)
	
	return PLUGIN_HANDLED
}

public event_new_round() {
	g_freezetime = true
	g_spawn = true
	g_planting = false
	g_carrier = 0
}

public event_got_bomb(id) {
	g_carrier = id
}

public event_bar_time(id) {
	if (id == g_carrier) {
		g_planting = bool:read_data(1)
		get_user_origin(id, g_pos[id])
		g_time[id] = 0
	}
}

public event_bomb_drop() {
	g_spawn = false
	g_planting = false
	g_carrier = 0
}

public logevent_round_start() {
	new id[32], num
	get_players(id, num, "ae", TEAM)

	if (!num) // is server empty?
		return

	g_freezetime = false

	// update afk timers and current positions
	new x
	for (new i = 0; i < num; ++i) {
		x = id[i]
		get_user_origin(x, g_pos[x])
		g_time[x] = 0
	}
}

public task_afk_check() {
	if (g_freezetime) // is freezetime right now?
		return

	// afk check
	new id[32], num, x, origin[3]
	get_players(id, num, "ae", TEAM)
	for (new i = 0; i < num; ++i) {
		x = id[i]
		get_user_origin(x, origin)
		if (origin[0] != g_pos[x][0] || origin[1] != g_pos[x][1] || (x == g_carrier && g_planting)) {
			g_time[x] = 0
			g_pos[x][0] = origin[0]
			g_pos[x][1] = origin[1]
			if (g_spawn && x == g_carrier)
				g_spawn = false
		}
		else
			g_time[x]++
	}

	// is bomb not currently carried or Ts number less than 2?
	if (!g_carrier || num < 2)
		return

#if defined CVAR_POINTERS
	new max_time = get_pcvar_num(g_spawn ? g_pcvar_spawn : g_pcvar_time)
#else
	new max_time = get_cvar_num(g_spawn ? "afk_bombtransfer_spawn" : "afk_bombtransfer_time")
#endif

	// is plugin disabled (cvar <= 0) or carrier isn't afk?
	if (max_time <= 0 || g_time[g_carrier] < max_time)
		return

	// find who from non-afk Ts is the closest to the afk carrier
	get_user_origin(g_carrier, origin)
	new min_dist = 999999, dist, recipient, origin2[3]
	for (new i = 0; i < num; ++i) {
		x = id[i]
		if (g_time[x] < max_time) {
			get_user_origin(x, origin2)
			dist = get_distance(origin, origin2)
			if (dist < min_dist) {
				min_dist = dist
				recipient = x
			}
		}
	}

	if (!recipient) // is all Ts afk?
		return

	new carrier = g_carrier
	engclient_cmd(carrier, "drop", WEAPON) // drop the backpack
	new c4 = engfunc(EngFunc_FindEntityByString, -1, "classname", WEAPON) // find weapon_c4 entity
	if (!c4)
		return

	new backpack = pev(c4, pev_owner) // get backpack entity
	if (backpack <= g_maxplayers)
		return

	// my backpack transfer trick (improved)
	set_pev(backpack, pev_flags, pev(backpack, pev_flags) | FL_ONGROUND)
	dllfunc(DLLFunc_Touch, backpack, recipient)

	// hud messages stuff below
	set_hudmessage(0, 255, 0, 0.35, 0.8, _, _, MSG_TIME)
	new message[128], c_name[32], r_name[32]
	get_user_name(carrier, c_name, 31)
	get_user_name(recipient, r_name, 31)
	formatex (message, 127, "%L", carrier, "AFK_BOMB_TRANSFERED", r_name, c_name)
	for (new i = 0; i < num; ++i)
	{
		if (get_cvar_num ("afk_hud_messages"))
			show_hudmessage(id[i], "%s", message)
		else
			client_print (id[i], print_chat, "%s", message)
	}

	set_hudmessage(255, 255, 0, 0.42, 0.3, _, _, MSG_TIME, _, _, 3)
	new message1[64]
	formatex (message1, 63, "%L", recipient, "AFK_YOU_GOT_BOMB")
	if (get_cvar_num ("afk_hud_messages"))
		show_hudmessage(recipient, message1)
	else
		client_print (recipient, print_chat, "%s", message)
}