#include <amxmodx>
#include <amxmisc>
#include <kz>
#include <respawn>
#include <time>

//#define DEBUG

#define MAX_CP 25
#define DISTANCE_FROM_BUTTON 95.0

// Check Point
new cpCount[33];
new cp_count[33] = {-1, ...};
new Float:cp_location[33][MAX_CP][3];

// Teleport Count
new gc_count[33];

// Is Player started?
new bool:Started[33];

// Timer
new Float:Timer[33];

// Pause Hold timer
new IsPaused[33];
new Float:PauseTime[33];

// Completed Climb?
new bool:completed_climb[33];

// Hold the entity id and origin of the start and end buttons
new start;
new Float:start_origin[3];

new end;
new Float:end_origin[3];

// Maximum numbers of slots
new maxplayers;

// Pointer Cvar
new kz_enabled;
new kz_CpPrice;
new mp_startmoney;

// Color chat
new SayText;
new RoundTime;

new bool:kz_died[33];

// Hud Stuff
new MsgSync;

// Forwards
new kz_fwd_init;
new kz_fwd_start;
new kz_fwd_finish;
new kz_fwd_reset;
new fwd_result;

new help_file[64];

// Map Exploits.
// One in kz_man_redrock. If they go into low gravity room then. Stop timer????
// What to do ? When they touch that trigger teleport disable the teleport and check points?

public plugin_init()
{
	register_plugin("KZ Main", KZ_VERSION, "teame06");

	// Forwards
	kz_fwd_init = CreateMultiForward("kz_Init", ET_IGNORE, FP_CELL);

	// Cvars
	register_cvar("kz_version", KZ_VERSION, FCVAR_SERVER|FCVAR_SPONLY);
	kz_enabled = register_cvar("kz_enabled", "1", FCVAR_SERVER|FCVAR_SPONLY);
	kz_CpPrice = register_cvar("kz_cp_price", "0");
	register_cvar("kz_boosttime", "45");
	register_cvar("kz_autojoin", "0");

	#if defined DEBUG
	register_clcmd("show_ent", "cmdEnt");
	#endif
}

public plugin_cfg()
{
	// Configs
	new config[64];
	get_configsdir(config, 63);

	server_cmd("exec %s/kz/kz.cfg", config);
	server_exec();

	set_task(6.5, "kz_start");
}

public kz_start()
{
	if(find_buttons() && get_pcvar_num(kz_enabled))
		ExecuteForward(kz_fwd_init, fwd_result, _ON);
	else
	{
		set_cvar_num("kz_enabled", 0);
		ExecuteForward(kz_fwd_init, fwd_result, _OFF);
	}
}

public kz_Init(_state)
{
	if(!_state)
	{
		pause("a");
		return;
	}

	// Forwards
	kz_fwd_start = CreateMultiForward("kz_StartClimb", ET_IGNORE, FP_CELL);
	kz_fwd_finish = CreateMultiForward("kz_FinishClimb", ET_IGNORE, FP_CELL, FP_CELL, FP_CELL, FP_CELL);
	kz_fwd_reset = CreateMultiForward("kz_ResetClimb", ET_IGNORE, FP_CELL);

	// Make a checkpoint
	//new _check[] = "Makes
	register_saycmd("cp", "cmdCP", -1, "");
	register_saycmd("checkpoint", "cmdCP", -1, "");
	register_saycmd("check", "cmdCP", -1, "");

	// Teleport to checkpoint
	register_saycmd("tele", "cmdTele", -1, "");
	register_saycmd("gc", "cmdTele", -1, "");
	register_saycmd("gocheck", "cmdTele", -1, "");
	register_saycmd("tp", "cmdTele", -1, "");

	// Timer stuff
	register_saycmd("pause", "cmdPause", -1, "");
	register_saycmd("reset", "cmdReset", -1, "");
	register_saycmd("restart", "cmdReset", -1, "");

	// Previous Check Point
	register_saycmd("stuck", "cmdStuck", -1, "");
	register_saycmd("unstuck", "cmdStuck", -1, "");

	// Misc
	register_saycmd("help", "cmdHelp", -1, "");

	// Admin Commands
	register_clcmd("amx_teleport", "cmdTeleport", ADMIN_BAN, "- amx_teleport <#userid|name|steamid> <#userid|name|steamid>");

	// Forwards
	register_forward(FM_PlayerPreThink, "PreThink", 1);
	register_forward(FM_ClientKill, "fm_ClientKill");

	// Dictionary files
	register_dictionary("time.txt");

	maxplayers = get_maxplayers();
	MsgSync = CreateHudSyncObj();
	SayText = get_user_msgid("SayText"); // Color messages
	RoundTime = get_user_msgid("RoundTime"); // Climb Timer
	mp_startmoney = get_cvar_pointer("mp_startmoney");

	get_configsdir(help_file, 63);
	formatex(help_file, 63, "%s/kz/help.txt", help_file);

	set_task(0.5, "hudtime", _, _, _, "b");

	set_cvar_num("mp_limitteams", 0);
	set_cvar_num("mp_autoteambalance", 0);
	set_cvar_num("mp_freezetime", 0);
	set_cvar_num("sv_airaccelerate", 10);
	set_cvar_num("sv_restartround", 1);
	set_cvar_num("respawn_enabled", 1); // Enables the respawn plugin on run.
	server_exec();
}

// This function was created to detect which maps are KZ
// but to also cache some button information for speed.
find_buttons()
{
	new ent = engfunc(EngFunc_FindEntityByString, -1, "classname", "func_button");

	while(ent > 0)
	{
		new target[32];
		pev(ent, pev_target, target, 31);

		if(equal(target, "counter_start"))
			start = ent;
		else if(equal(target, "clockstartbutton"))
			start = ent;
		else if(equal(target, "firsttimerelay"))
			start = ent;


		if(equal(target, "clockstop"))
			end = ent;
		else if(equal(target, "counter_off"))
			end = ent;
		else if(equal(target, "clockstopbutton"))
			end = ent;

		ent = engfunc(EngFunc_FindEntityByString, ent, "classname", "func_button");
	}

	if(start && end)
	{
		new map[32];
		get_mapname(map, 31);

		if(equali("surf_", map, 5))
			return 0;

		_get_brush_entity_origin(start, start_origin);
		_get_brush_entity_origin(end, end_origin);

		return 1;
	}
	return 0;
}

public plugin_natives()
{
	register_library("kz");
	register_native("IsPlayerStarted", "native_is_player_started");
	register_native("IsPlayerFinished", "native_is_player_finish");

	// This has to be style 1. It be to complicated if it was style 0;
	register_native("kz_ShowSyncHudMsg", "native_showhud", 1);
	register_native("kz_ColorChat", "native_color_print_chat", 1);
}

public native_color_print_chat(player, color_type, const message[], {Float,Sql,Result,_}:...)
{
	new nums = numargs();

	if(nums < 3)
		return log_error(AMX_ERR_NATIVE, "Bad native parameters");

	new i;
	for(i = 3; i <= nums; i++)
		param_convert(i);

	static msg[191];
	vformat(msg, 190, message, 4);

	if(player > 0 && player <= maxplayers)
		_color_chat(player, color_type, msg);
	else if(!player)
	{
		static players[32], nums, index;
		get_players(players, nums);

		for(new i = 0; i < nums; i++)
		{
			index = players[i];
			_color_chat(index, color_type, msg);
		}
	}
	return PLUGIN_CONTINUE;
}

public native_showhud(player, r, g, b, Float:x, Float:y, const message[], {Float,Sql,Result,_}:...)
{
	new nums = numargs();

	if(nums < 7)
		return log_error(AMX_ERR_NATIVE, "Bad native parameters");

	new i;
	for(i = 7; i <= nums; i++)
		param_convert(i);

	static msg[191];
	vformat(msg, 190, message, 8);
	kz_show_hud(player, r, g, b, x, y, msg);

	return PLUGIN_CONTINUE;
}

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

	return Started[get_param(1)];
}

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

	return completed_climb[get_param(1)];
}

public client_putinserver(id)
{
	if(!get_pcvar_num(kz_enabled))
		return;

	set_task(10.0, "advert_help", id);
}

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

	Started[id] = false;
	completed_climb[id] = false;

	Timer[id] = 0.0;
	cp_count[id] = -1;
	PauseTime[id] = 0.0;

	kz_died[id] = false;
	gc_count[id] = 0;
	cpCount[id] = 0;
}

public advert_help(id)
{
	_color_chat(id, green, "[kz] For kz commands please type say /help");
}

public cmdPause(id)
{
	if(get_pcvar_num(kz_enabled))
	{
		if(Started[id])
		{
			new temp = IsPaused[id];
			if(!temp || temp == 2)
			{
				PauseTime[id] += (thetime() - Timer[id]);
				IsPaused[id] = 1;
				Timer[id] = 0.0;

				kz_show_hud(id, 128, 0, 128, 0.43, 0.89, "PAUSED");
				set_pev(id, pev_flags, pev(id, pev_flags) | FL_FROZEN);

				SetPlayerNotSolid(id);
				SetPlayerPause(id, 1);
			}
			else
			{
				IsPaused[id] = (IsPaused[id] == 1) ? 2 : 1;
				Timer[id] = thetime();

				kz_show_hud(id, 128, 0, 128, 0.44, 0.89, "UNPAUSED");
				set_pev(id, pev_flags, pev(id, pev_flags) & ~FL_FROZEN);

				SetPlayerSolid(id);
				SetPlayerPause(id);
			}
		}
		else
		{
			kz_show_hud(id, 128, 0, 128, 0.29, 0.89, "You must start the timer before you can pause.");
		}
		return PLUGIN_HANDLED;
	}
	return PLUGIN_CONTINUE;
}

public cmdReset(id)
{
	if(get_pcvar_num(kz_enabled))
	{
		Started[id] = false;
		reset_data(id);
		ExecuteForward(kz_fwd_reset, fwd_result, id);
		spawn_player(id);

		return PLUGIN_HANDLED;
	}
	return PLUGIN_CONTINUE;
}

public reset_data(id)
{
	Timer[id] = 0.0;
	PauseTime[id] = 0.0;
	cpCount[id] = 0;
	gc_count[id] = 0;
}

public cmdHelp(id)
{
	if(get_pcvar_num(kz_enabled))
	{
		show_motd(id, help_file);
		return PLUGIN_HANDLED;
	}
	return PLUGIN_CONTINUE;
}

public PreThink(id)
{
	if(!get_pcvar_num(kz_enabled) && !is_user_alive(id))
		return FMRES_IGNORED;

	if(pev(id, pev_button) & IN_USE)
	{
		static button, body;
		get_user_aiming(id, button, body);

		if(button > maxplayers)
		{
			if(pev_valid(button))
			{
				static Float:origin[3];
				pev(id, pev_origin, origin);

				if(!Started[id] && button == start)
				{
					if(vector_distance(origin, start_origin) <= DISTANCE_FROM_BUTTON)
					{
						start_climb(id, origin);
					}
				}
				else if(button == end)
				{
					if(vector_distance(origin, end_origin) <= DISTANCE_FROM_BUTTON)
					{
						finish_climb(id);
					}
				}
			}
		}
	}

	// Checks if a player is in noclip. Do not allow him to continue the timer.
	/*if(Started[id] && pev(id, pev_movetype) == MOVETYPE_NOCLIP)
	{
		reset_data(id);
		Started[id] = false;
		kz_ColorChat(id, green, "[kz] NoClip Detected, Timer Stopped.");
	}*/
	return FMRES_IGNORED;
}

is_not_alive(id)
{
	kz_show_hud(id, 128, 0, 128, 0.29, 0.89, "You must be alive to execute this command.");
}

start_climb(id, Float:origin[3])
{
	static Float:angle[3];
	pev(id, pev_angles, angle);

	Started[id] = true;
	spawn_player(id); // Remove any long jump, etc...

	set_pev(id, pev_angles, angle);
	set_pev(id, pev_origin, origin);
	delay_duck(id);

	dllfunc(DLLFunc_Use, start, id); // Make sure the map timer starts
	Timer[id] = thetime();

	kz_show_hud(id, 128, 0, 128, 0.38, 0.89, "Timer started. Go Go Go!!!");
	client_cmd(id, "spk scientist/c3a2_sci_portopen");

	if(get_pcvar_num(kz_CpPrice))
		cs_set_user_money(id, get_pcvar_num(mp_startmoney));

	cp_count[id] = -1;
	IsPaused[id] = 0;

	ExecuteForward(kz_fwd_start, fwd_result, id);

	cs_set_user_deaths(id, 0);
	set_pev(id, pev_frags, 0.0);
}

finish_climb(id)
{
	if(!is_user_alive(id))
	{
		is_not_alive(id);
		return;
	}

	if(!completed_climb[id] || Started[id])
	{
		completed_climb[id] = true;

		static time;

		if(Started[id])
		{
			dllfunc(DLLFunc_Use, end, id); // Make sure the map timer stops

			time = floatround((thetime() - Timer[id]) + PauseTime[id]);

			_convert_time(id, time);

			client_cmd(id, "spk woop");
			kz_show_hud(id, 128, 0, 128, 0.46, 0.89, "Finished.");
		}

		Started[id] = false;

		ExecuteForward(kz_fwd_finish, fwd_result, id, time, cpCount[id], gc_count[id]);
		reset_data(id);
	}
}

_convert_time(id, time)
{
	static TimeMsg[128];

	static name[32];
	get_user_name(id, name, 31);

	// Display to everyone with server language.
	get_time_length(0, time, timeunit_seconds, TimeMsg, 127);
	kz_ColorChat(0, team, "[kz] It took %s %s to complete the climb.", name, TimeMsg);
}

public cmdCP(id)
{
	if(get_pcvar_num(kz_enabled))
	{
		if(is_user_alive(id))
		{
			if(IsPaused[id] == 1)
			{
				paused(id);
				return PLUGIN_HANDLED;
			}

			if(get_pcvar_num(kz_CpPrice))
			{
				new money = cs_get_user_money(id);

				if(get_pcvar_num(kz_CpPrice) <= money)
					cs_set_user_money(id, money - get_pcvar_num(kz_CpPrice));
				else
				{
					kz_ShowSyncHudMsg(id, 128, 0, 128, 0.25, 0.89, "You don't have enough cash for more checkpoints. $%i", get_pcvar_num(kz_CpPrice));
					return PLUGIN_HANDLED;
				}
			}

			static vel[3];
			pev(id, pev_velocity, vel);

			if(vel[2] >= 0 && pev(id, pev_flags) & FL_ONGROUND)
			{
				cp_count[id]++;
				cpCount[id]++;

				if(cp_count[id] == MAX_CP)
					cp_count[id] = 0;

				pev(id, pev_origin, cp_location[id][cp_count[id]]);
				kz_ShowSyncHudMsg(id, 128, 0, 128, 0.41, 0.89, "Checkpoint saved. %i", cpCount[id]);
			}
			else
			{
				kz_show_hud(id, 128, 0, 128, 0.32, 0.89, "You can't make checkpoints while falling.");
			}
		}
		else
		{
			is_not_alive(id);
		}
		return PLUGIN_HANDLED;
	}
	return PLUGIN_CONTINUE;
}

paused(id)
{
	kz_show_hud(id, 128, 0, 128, 0.29, 0.89, "You can't execute this command while paused.");
}

public cmdTele(id)
{
	if(get_pcvar_num(kz_enabled))
	{
		if(!is_user_alive(id))
		{
			is_not_alive(id);
			return PLUGIN_HANDLED;
		}

		if(IsPaused[id] == 1)
		{
			paused(id);
			return PLUGIN_HANDLED;
		}

		if(cp_count[id] != -1)
		{
			static Float:origin[3], players[32], nums, person, i, Float:cp_origin[3];
			get_players(players, nums, "ac");
			SetPlayerNotSolid(id);

			cp_origin = cp_location[id][cp_count[id]];

			for(i = 0; i < nums; i++)
			{
				person = players[i];

				if(id == person)
					continue;

				pev(person, pev_origin, origin);

				if(vector_distance(origin, cp_origin) <= 74.0)
				{
					SetPlayerNotSolid(person);
					break;
				}
			}

			set_pev(id, pev_velocity, {0, 0, 0}); // Reset velocity
			set_pev(id, pev_origin, cp_origin);
			kz_show_hud(id, 128, 0, 128, 0.29, 0.89, "You have been teleported to your checkpoint.");
			delay_duck(id);

			gc_count[id]++;
		}

		return PLUGIN_HANDLED;
	}
	return PLUGIN_CONTINUE;
}

delay_duck(id)
{
	set_task(0.01, "force_duck", id);
	_fm_set_entity_flags(id, FL_DUCKING, 1);
}

public force_duck(id)
{
	_fm_set_entity_flags(id, FL_DUCKING, 1);
}

public cmdStuck(id)
{
	if(!get_pcvar_num(kz_enabled))
		return PLUGIN_CONTINUE;

	if(!is_user_alive(id))
	{
		is_not_alive(id);
		return PLUGIN_HANDLED;
	}

	if(IsPaused[id] == 1)
	{
		paused(id);
		return PLUGIN_HANDLED;
	}

	if(cp_count[id] == -1)
		return PLUGIN_HANDLED;

	if(cp_count[id] > 0)
		cp_count[id]--;

	set_pev(id, pev_velocity, {0, 0, 0}); // Reset velocity
	kz_show_hud(id, 128, 0, 128, 0.30, 0.89, "Your previous checkpoint has been restored.");
	set_pev(id, pev_origin, cp_location[id][cp_count[id]]);
	delay_duck(id);
	return PLUGIN_HANDLED;
}

#if defined DEBUG
public cmdEnt(id)
{
	new target_id, body;
	get_user_aiming(id, target_id, body);

	if(target_id > maxplayers)
	{
		new class[32];
		pev(target_id, pev_classname, class, 31);

		new target[32];
		pev(target_id, pev_target, target, 31);

		client_print(id, print_chat, "ent id: %i classname: %s target: %s", target_id, class, target);
	}
	return PLUGIN_HANDLED;
}
#endif

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

_color_chat(id, color, message[])
{
	if(!id)
		return;

	if(is_user_connected(id))
	{
		static msg[192];

		switch(color)
		{
			case 1:
				msg = "^x03";
			case 2:
				msg = "^x04";
		}

		add(msg, 191, message);

		message_begin(MSG_ONE, SayText, {0, 0, 0}, id);
		write_byte(id);
		write_string(msg);
		message_end();
	}
}

_fm_set_entity_flags(index, flag, onoff) // Taken from fakemeta_util
{
	new flags = pev(index, pev_flags);

	if ((flags & flag) > 0)
		return onoff == 1 ? 2 : 1 + 0 * set_pev(index, pev_flags, flags - flag);
	else
		return onoff == 0 ? 2 : 1 + 0 * set_pev(index, pev_flags, flags + flag);

	return 0;
}

public cmdTeleport(id, level, cid)
{
	if(!get_pcvar_num(kz_enabled))
		return PLUGIN_CONTINUE;

	if(!cmd_access(id, level, cid, 1))
		return PLUGIN_HANDLED;

	new arg[32], arg1[32];
	read_argv(1, arg, 31);
	read_argv(2, arg1, 31);

	new person = cmd_target(id, arg, 12);
	new player = cmd_target(id, arg1, 12);

	if(!person || !player)
		return PLUGIN_HANDLED;

	new Float:Origin[3];

	pev(player, pev_origin, Origin);
	set_pev(person, pev_origin, Origin);
	SetPlayerNotSolid(player);
	SetPlayerNotSolid(person);
	delay_duck(person);

	new name[32];
	new name1[32];

	get_user_name(person, name, 31);
	get_user_name(person, name1, 31);

	console_print(id, "[kz] You teleport %s to player %s position.", name, name1);
	return PLUGIN_HANDLED;
}

public DeathMessage(killer, victim, headshot, weaponname[])
{
	if(get_pcvar_num(kz_enabled))
		kz_died[victim] = true;
}

public PostSpawn(id)
{
	if(!get_pcvar_num(kz_enabled))
		return;

	if(!is_user_alive(id))
		return;

	if(kz_died[id])
	{
		kz_died[id] = false;
		cmdTele(id);
	}
}

kz_show_hud(id, r, g, b, Float:x, Float:y, const msg[])
{
	if(is_user_connected(id)) // 128, 0, 128
	{
		set_hudmessage(r, g, b, x, y, 0, 0.0, 3.0, 0.0, 0.0, 4);
		ShowSyncHudMsg(id, MsgSync, "%s", msg);
	}
}

public fm_ClientKill(id)
{
	if(get_pcvar_num(kz_enabled))
		return FMRES_SUPERCEDE;

	return FMRES_IGNORED;
}

public hudtime()
{
	if(get_pcvar_num(kz_enabled))
	{
		static Players[32], count, i, index;
		get_players(Players, count, "ach");

		for(i = 0; i < count; i++)
		{
			index = Players[i];

			message_begin(MSG_ONE, RoundTime, {0, 0, 0}, index);
			write_short(func_29(index) + 1);
			message_end();
		}
	}
}

func_29(id)
{
	static Float:blah;
	blah = 0.0;

	switch(IsPaused[id])
	{
		case 0:
		{
			if(Timer[id])
				blah = thetime() - Timer[id];
		}
		case 1:
		{
			blah = PauseTime[id];
		}
		case 2: // Player continue after paused.
		{
			blah = PauseTime[id] + (thetime() - Timer[id]);
		}
	}
	return floatround(blah);
}