/* Plugin generated by AMXX-Studio */

#include <amxmodx>
#include <fakemeta>

#define PLUGIN "KZ Stats"
#define VERSION "inprogress"
#define AUTHOR "SchlumPF"

//#pragma semicolon 1

/* =================================================================================================
http://xtreme-jumps.eu/e107_plugins/forum/forum_viewtopic.php?123955.0

[b]This plugin is  [color=#00ff00]SAFE[/color] for demo recording. -SchlumPF[/b]

[u][b]KZ Stats (not yet finished)[/b][/u]

[b]Description[/b]
KZ Stats is a small LjStats without any Top15 or public messages which spam the chat. It has no protection against any CVar so you can even get stats with 'developer 1' or 'cl_sidespeed 300'.

[b]Advantages against other LjStats'[/b]
- Public sourcecode
- A lot more optimized than other LjStats
- I have a lot of time to work on this :rofl:

[b]Commands[/b]
say /ljstats - enables/disables stats, after connect stats are disabled
kz_stats_coords "<x> <y>" - admins only, sets hudmessage position
kz_stats_color "<r> <g> <b>" - admins only, sets hudmessage color

[b]Supportes Techniques[/b]
- LongJump
- HighJump
- CountJump
- BhopLongJump
- Ducked BhopLongJump
- Standup BhopLongJump
- Multi Countjump
- Weird LongJump (doesn't show up as Wj but stats are correct)

[b]Known Bugs[/b]
- Stats can appear while multi bhopping
- Distance can be wrong on landing on a func_door, func_train etc.

[b]Planned Features[/b]
- Support for
[list]*Weird LongJumps
*Drop CountJumps
*Drop BhopLongJumps
*Ladder LongJumps[/list]
- Strafecounter
- Strafestats
- Perfect/More correct failed stats
- Anticheat against some special entitys
- Stats for spectators

[b]Explanation of the Stats[/b]
[url=http://todome.de/schlumpf/plugins/kz_stats/stats.JPG]Screenshoot of stats[/url]
1. Technique, a 'Failed' is added on failed stats
2. Distance in units
3. Maxspeed in units/second
4. Gain (maxspeed - prestrafe) in units/second
5. Prestrafe in units/second
6. Maximum possible prestrafe with current weapon/technique in units/second
7. Syncronisation (100% correct in opposite to sync in other ljstats') in %
8. Strafecounter
9. Heightdifference between jumpoff and landing in units/second, will just be shown on bhops

[url=http://todome.de/schlumpf/plugins/kz_stats/kz_stats.rar][size=20]DOWNLOAD[/size][/url]

[b]Credits[/b]
Fatalis - For the very first LjStats
NumB - For his LjStats-XM modification, the Hj detection and answering my questions about LjStats
Lt.RAT - For the failed stats code, the idea for Bhop detection, maxprestrafe calculation and answering my questions about LjStats
eDark - Answering my questions about engine
eMTi - For nothing and because I love him <3
 
[size=20][color=#ff0000][b]ATTENTION![/b][/color][/size]
Please post bugs you found in this thread! Explain what happend, if possible record a [b]small[/b] demo and give me a error log, if available. To get a error log which would help me a lot it is very important to add this plugin in plugins.ini like this:
[blockquote]kz_stats.amxx [b]debug[/b][/blockquote]

Critism and suggestions are welcome :D
================================================================================================= */

/* =================================================================================================
ToDo:
	Detections:
		Weird LongJump
		Drop CountJump
		Drop BhopLongJump
		Ladder Lj
		
	Features:
		Strafecounter
		Strafestats
		Perfect failed stats
		Anticheat against some entitys
		
	Change operators to floatx-natives for better speed
================================================================================================= */

#define MIN_DISTANCE 200.0
#define MAX_DISTANCE 275.0

enum JumpType
{
	Type_LongJump = 0,
	Type_HighJump = 1,
	Type_CountJump = 2,
	Type_BhopLongJump = 3,
	Type_DuckBhopLongJump = 4,
	Type_StandupBhopLongJump = 5,
	
	Type_WeirdLongJump = 6,
	Type_Drop_BhopLongJump = 7,
	Type_Drop_CountJump = 8,
	
	Type_Multi_CountJump = 9,
	Type_Multi_Bhop = 10
}

new g_b__lj_stats[33];
new g_b__reset[33];

new Float:g_f__coords[2] = { -1.0, -0.2 };
new g_i__colors[3] = { 0, 250, 180 };

new g_p__sv_gravity;

public plugin_init( )
{
	register_plugin( PLUGIN, VERSION, AUTHOR );
	register_clcmd( "say /ljstats", "cmdljStats" );
	
	register_clcmd( "kz_stats_coords", "cmdCoords" );
	register_clcmd( "kz_stats_color", "cmdColor" );
	
	register_forward( FM_PlayerPreThink, "fwdPlayerPreThink_Pre", 0 );
	g_p__sv_gravity = get_cvar_pointer( "sv_gravity" );
}

public fwdPlayerPreThink_Pre( plr )
{
	if( !is_user_alive( plr ) || !g_b__lj_stats[plr] )
	{
		return FMRES_IGNORED;
	}
	
	
	static JumpType:jump_type[33];
	static frames[33], frames_gained_speed[33];
	static bool:in_air[33], bool:in_duck[33], bool:in_bhop[33];
	static bool:failed_jump[33], bool:failed_ducking[33];
	static bool:first_frame[33], bool:started_cj_pre[33], bool:started_multicj_pre[33];
	static Float:maxspeed[33], Float:prestrafe[33];
	static Float:frame_origin[33][2][3], Float:frame_velocity[33][2][3];
	static Float:duckoff_time[33], Float:jumpoff_time[33], Float:last_land_time[33];
	static Float:duckoff_origin[33][3], Float:jumpoff_origin[33][3], Float:pre_jumpoff_origin[33][3];
	static Float:failed_origin[33][3], Float:jumpoff_foot_height[33];
	
	
	if( g_b__reset[plr] )
	{
		g_b__reset[plr] = false;
		
		in_air[plr] = false;
		in_duck[plr] = false;
		in_bhop[plr] = false;
		
		failed_jump[plr] = false;
		
		frames_gained_speed[plr] = 0;
		frames[plr] = 0;
		
		started_multicj_pre[plr] = false;
		started_cj_pre[plr] = false;
		
		jump_type[plr] = Type_LongJump;
	}

	
	static button, oldbuttons, flags;
	button = pev( plr, pev_button );
	flags = pev( plr, pev_flags );
	oldbuttons = pev( plr, pev_oldbuttons );
	
	static Float:origin[3];
	pev( plr, pev_origin, origin );
	
	static Float:velocity[3], Float:speed;
	pev( plr, pev_velocity, velocity );
	speed = get_speed( velocity );
	
	if( in_air[plr] && !( flags & FL_ONGROUND ) && !failed_jump[plr] )
	{
		static Float:old_speed[33];
		if( speed > old_speed[plr] )
		{
			frames_gained_speed[plr]++;
		}
		frames[plr]++;
		
		old_speed[plr] = speed;
		
		if( speed > maxspeed[plr] )
		{
			maxspeed[plr] = speed;
		}
		
		if( in_bhop[plr] ? floatsub( origin[2] + 18.0, pre_jumpoff_origin[plr][2] ) < 0 : floatsub( origin[2] + 18.0, jumpoff_origin[plr][2] ) < 0 )
		{
			failed_jump[plr] = true;
			
			failed_jump[plr] = true;
			failed_ducking[plr] = is_user_ducking( plr );
			
			static Float:jumpoff_z_origin;
			jumpoff_z_origin = jumpoff_origin[plr][2];
			if( failed_ducking[plr] )
			{
				jumpoff_z_origin -= 18.0;
				origin[2] -= 18.0;
			}
			
			static Float:temp;
			temp = ( jumpoff_z_origin - frame_origin[plr][1][2] ) / ( origin[2] - frame_origin[plr][1][2] );

			failed_origin[plr][0] = temp * ( origin[0] - frame_origin[plr][1][0] ) + frame_origin[plr][1][0];
			failed_origin[plr][1] = temp * ( origin[1] - frame_origin[plr][1][1] ) + frame_origin[plr][1][1];
			failed_origin[plr][2] = jumpoff_z_origin;
			
			return FMRES_IGNORED;
		}
		
		if( first_frame[plr] )
		{
			first_frame[plr] = false;
			
			frame_origin[plr][0] = origin;
			frame_velocity[plr][0] = velocity;
			
			if( in_bhop[plr] )
			{
				if( velocity[2] < 229.0 )
				{
					jump_type[plr] = Type_BhopLongJump;
				}
				else if( velocity[2] )
				{
					if( button & IN_DUCK && oldbuttons & IN_DUCK )
					{
						jump_type[plr] = Type_DuckBhopLongJump;
					}
					else
					{
						jump_type[plr] = Type_StandupBhopLongJump;
					}
				}
				
				jumpoff_origin[plr][2] = pre_jumpoff_origin[plr][2];
			}
		}
		else
		{
			frame_origin[plr][1] = origin;
			frame_velocity[plr][1] = velocity;
		}
	}

	if( !( !( button & IN_JUMP ) && !( oldbuttons & IN_JUMP ) ) && flags & FL_ONGROUND && in_air[plr] )
	{
		static bool:ducking;
		ducking = is_user_ducking( plr );
		
		static classname[32];
		pev( pev( plr, pev_groundentity ), pev_classname, classname, 31 );
		
		static Float:height_difference;
		height_difference = floatabs( ducking ? jumpoff_origin[plr][2] - origin[2] - 18.0 : jumpoff_origin[plr][2] - origin[2] );
		
		if( equal( classname, "func_door" ) && height_difference >= 1.0 )
		{
			g_b__reset[plr] = true;
			return FMRES_IGNORED;
		}
		else if( !equal( classname, "func_door" ) && height_difference )
		{
			g_b__reset[plr] = true;
			return FMRES_IGNORED;
		}
		
		in_bhop[plr] = true;
		
		pre_jumpoff_origin[plr] = jumpoff_origin[plr];
		jumpoff_foot_height[plr] = ducking ? origin[2] - 18.0 : origin[2] - 36.0;
		
		jumpoff_time[plr] = get_gametime( );
		jumpoff_origin[plr] = origin;
		
		first_frame[plr] = true;
		
		prestrafe[plr] = speed;
		maxspeed[plr] = speed;
	}
	else if( button & IN_JUMP && !( oldbuttons & IN_JUMP ) && flags & FL_ONGROUND && !in_air[plr] )
	{
		jumpoff_time[plr] = get_gametime( );
		jumpoff_origin[plr] = origin;
		
		if( jump_type[plr] == Type_CountJump || jump_type[plr] == Type_Multi_CountJump )
		{
			if( ( button & IN_DUCK && oldbuttons & IN_DUCK ) && ( 0.35 > jumpoff_time[plr] - duckoff_time[plr] > 0.3 ) )
			{
				// russian walking + bhop after it :S currently shown as cj but is it really a cj? :D
			}
			else if( jumpoff_time[plr] - duckoff_time[plr] >= 0.3 )
			{
				client_print( plr, print_chat, "%f %i %i", jumpoff_time[plr] - duckoff_time[plr], button & IN_DUCK ? 1 : 0, oldbuttons & IN_DUCK ? 1 : 0 )
				jump_type[plr] = Type_LongJump;
			}
			else
			{
				static bool:ducking;
				ducking = is_user_ducking( plr );
				
				static classname[32];
				pev( pev( plr, pev_groundentity ), pev_classname, classname, 31 );
				
				static Float:height_difference;
				height_difference = floatabs( ducking ? jumpoff_origin[plr][2] - origin[2] - 18.0 : jumpoff_origin[plr][2] - origin[2] );
				
				if( equal( classname, "func_door" ) && height_difference >= 1.0 )
				{
					g_b__reset[plr] = true;
					return FMRES_IGNORED;
				}
				else if( !equal( classname, "func_door" ) && height_difference )
				{
					g_b__reset[plr] = true;
					return FMRES_IGNORED;
				}
			}
		}
		
		in_air[plr] = true;
		first_frame[plr] = true;
		
		prestrafe[plr] = speed;
		maxspeed[plr] = speed;
	}
	else if( flags & FL_ONGROUND && in_air[plr] )
	{
		static bool:ducking;
		
		static type[32];
		type[0] = '^0';
		if( failed_jump[plr] )
		{
			formatex( type, 31, "Failed " );
			origin = failed_origin[plr];
			ducking = failed_ducking[plr];
		}
		else
		{
			ducking = is_user_ducking( plr );
		}
		
		last_land_time[plr] = get_gametime( );

		static Float:distance1;
		distance1 = get_distance_f( jumpoff_origin[plr], origin ) + 32.0625;
		
		/* maybe better? pev( plr, pev_gravity ) * get_pcvar_float( g_p__sv_gravity ) */
		static Float:gravity;
		gravity = get_pcvar_float( g_p__sv_gravity );
		
		static Float:_time;
		_time = floatdiv( floatsqroot( floatpower( frame_velocity[plr][0][2], 2.0 ) + ( 2 * gravity * ( frame_origin[plr][0][2] - origin[2] ) ) ) * -1.0 - frame_velocity[plr][1][2], gravity * -1.0 );
		
		if( frame_velocity[plr][1][0] < 0.0 ) frame_velocity[plr][1][0] *= -1.0;
		if( frame_velocity[plr][1][1] < 0.0 ) frame_velocity[plr][1][1] *= -1.0;
		
		static Float:x_difference, Float:y_difference;
		x_difference = _time * frame_velocity[plr][1][0];
		y_difference = _time * frame_velocity[plr][1][1];
		
		static Float:land_origin[3];
		land_origin[0] = frame_origin[plr][1][0] < origin[0] ? frame_origin[plr][1][0] + x_difference : frame_origin[plr][1][0] - x_difference;
		land_origin[1] = frame_origin[plr][1][1] < origin[1] ? frame_origin[plr][1][1] + y_difference : frame_origin[plr][1][1] - y_difference;
		land_origin[2] = ducking ? origin[2] + 18.0 : origin[2];
		
		static Float:distance2;
		distance2 = get_distance_f( jumpoff_origin[plr], land_origin ) + 32.0625;

		static Float:distance;
		distance = distance1 > distance2 ? distance2 : distance1;

		if( distance < MIN_DISTANCE || MAX_DISTANCE < distance )
		{
			g_b__reset[plr] = true;
			return FMRES_IGNORED;
		}
		
		if( jump_type[plr] == Type_LongJump )
		{
			static Float:start[3];
			
			start[0] = ( jumpoff_origin[plr][0] + land_origin[0] ) * 0.5;
			start[1] = ( jumpoff_origin[plr][1] + land_origin[1] ) * 0.5;
			start[2] = ducking ? origin[2] - 18.1 : origin[2] - 36.1;
				
			if( engfunc( EngFunc_PointContents, start ) == CONTENTS_EMPTY )
			{
				static Float:stop[3], Float:fraction, Float:max_fraction;
				
				stop[0] = jumpoff_origin[plr][0];
				stop[1] = jumpoff_origin[plr][1];
				if( start[0] < stop[0] )
				{
					stop[0] += stop[0] - start[0];
				}
				else
				{
					stop[0] -= start[0] - stop[0];
				}
				
				if( start[1] < stop[1] )
				{
					stop[1] += stop[1] - start[1];
				}
				else
				{
					stop[1] -= start[1] - stop[1];
				}
				stop[2] = start[2];
				
				jumpoff_origin[plr][2] -= 0.1;
				
				static Float:temp;
				temp = get_distance_f( jumpoff_origin[plr], start );
				max_fraction = floatdiv( temp + 16.03125, temp * 2.0 );
					
				engfunc( EngFunc_TraceHull, start, stop, IGNORE_MONSTERS, HULL_HUMAN, plr, 0 );
				get_tr2( 0, TR_flFraction, fraction );
				
				if( fraction < max_fraction )
				{
					get_tr2( 0, TR_vecEndPos, start );
					
					stop[0] = start[0];
					stop[1] = start[1];
					stop[2] = start[2] - 69.9;
					
					engfunc( EngFunc_TraceLine, start, stop, IGNORE_MONSTERS, plr, 0 );
					get_tr2( 0, TR_flFraction, fraction );
					
					if( fraction == 1.0 )
					{
						jump_type[plr] = Type_HighJump;
					}
				}
			}
		}
		
		switch( jump_type[plr] )
		{
			case 0: add( type, 31, "LongJump", 25 );
			case 1: add( type, 31, "HighJump", 25 );
			case 2: add( type, 31, "CountJump", 25 );
			case 3: add( type, 31, "Bhop LongJump", 25 );
			case 4: add( type, 31, "DuckBhop LongJump", 25 );
			case 5: add( type, 31, "StandupBhop LongJump", 25 );
			case 6: add( type, 31, "Weird LongJump", 25 );
			case 7: add( type, 31, "Drop Bhop LongJump", 25 );
			case 8: add( type, 31, "Drop CountJump", 25 );
			case 9: add( type, 31, "Multi CountJump", 25 );
			case 10: add( type, 31, "Multi Bhop", 25 );
		}

		static Float:sync, Float:gain;
		sync = floatdiv( float( frames_gained_speed[plr] * 100 ), float( frames[plr] ) );
		gain = floatsub( maxspeed[plr], prestrafe[plr] );
		
		set_hudmessage( g_i__colors[0], g_i__colors[1], g_i__colors[2], g_f__coords[0], g_f__coords[1], 0, 6.0, 2.5, 0.0, 0.0, 1 );
		
		if( jump_type[plr] == Type_BhopLongJump || jump_type[plr] == Type_DuckBhopLongJump || jump_type[plr] == Type_StandupBhopLongJump )
		{
			static Float:land_foot_height;
			land_foot_height = ducking ? origin[2] - 18.0 : origin[2] - 36.0;
		
			static Float:max_prestrafe;
			max_prestrafe = pev( plr, pev_maxspeed ) * 1.2;
		
			show_hudmessage( plr, "%s^nDistance: %f^nMaxspeed: %f (%.03f)^nPrestrafe: %f (%.03f)^nSync: %f^nStrafecounter: todo^nHeight difference: %f", type, distance, maxspeed[plr], gain, prestrafe[plr], max_prestrafe, sync, floatabs( jumpoff_foot_height[plr] - land_foot_height ) );
		}
		else
		{
			static Float:max_prestrafe;
			if( jump_type[plr] == Type_LongJump || jump_type[plr] == Type_HighJump )
			{
				max_prestrafe = pev( plr, pev_maxspeed ) * 1.115;
			}
			else
			{
				max_prestrafe = pev( plr, pev_maxspeed ) * 1.2;
			}
			
			show_hudmessage( plr, "%s^nDistance: %f^nMaxspeed: %f (%.03f)^nPrestrafe: %f (%.03f)^nSync: %f^nStrafecounter: todo", type, distance, maxspeed[plr], gain, prestrafe[plr], max_prestrafe, sync );
		}
		client_print( plr, print_console, "%s Distance %f Maxspeed %f Gain %f Prestrafe %f Sync %f", type, distance, maxspeed[plr], gain, prestrafe[plr], sync );
		
		g_b__reset[plr] = true;
	}
	
	if( !in_air[plr] && button & IN_DUCK && flags & FL_ONGROUND && !in_duck[plr] )
	{
		if( get_gametime( ) - duckoff_time[plr] < 0.3 )
		{
			started_multicj_pre[plr] = true;
		}
		else
		{
			started_cj_pre[plr] = true;
		}
		
		in_duck[plr] = true;
	}
	else if( !in_air[plr] && oldbuttons & IN_DUCK )
	{
		if( !is_user_ducking( plr ) )
		{
			in_duck[plr] = false;
			if( started_cj_pre[plr] )
			{
				started_cj_pre[plr] = false;
				
				duckoff_time[plr] = get_gametime( );
				duckoff_origin[plr] = origin;
				
				jump_type[plr] = Type_CountJump;
			}
			else if( started_multicj_pre[plr] )
			{
				started_multicj_pre[plr] = false;
				
				duckoff_time[plr] = get_gametime( );
				duckoff_origin[plr] = origin;
				
				jump_type[plr] = Type_Multi_CountJump;
			}
		}
	}
	
	return FMRES_IGNORED;
}

public client_command( plr )
{
	static command[32];
	read_argv( 0, command, 31 );
	
	static const forbidden[][] =
	{
		"tele", "tp", "gocheck", "gc", "stuck", "unstuck", "start", "reset", "restart",
		"spawn", "respawn"
	};
	
	if( equal( command, "say" ) )
	{
		read_args( command, 31 );
		remove_quotes( command );
	}
	
	if( equal( command, "+hook" ) )
	{
		g_b__reset[plr] = true;
	}
	else if( command[0] == '/' || command[0] == '.' )
	{
		copy( command, 31, command[1] );
		
		for( new i ; i < sizeof( forbidden ) ; i++ )
		{
			if( equal( command, forbidden[i] ) )
			{
				g_b__reset[plr] = true;
				break;
			}
		}
	}
}

public cmdljStats( plr )
{
	g_b__lj_stats[plr] = !g_b__lj_stats[plr];
	
	static saytext;
	if( !saytext )
	{
		saytext = get_user_msgid( "SayText" );
	}
	
	static msg[192];
	msg[0] = 0x04;

	formatex( msg[1], 190, "[XJ] LjStats are now %s", g_b__lj_stats[plr] ? "enabled" : "disabled" );
	
	message_begin( MSG_ONE_UNRELIABLE, saytext, { 0, 0, 0 }, plr );
	write_byte( plr );
	write_string( msg );
	message_end( );
}

public cmdCoords( plr )
{
	if( !( get_user_flags( plr ) & ADMIN_KICK ) )
	{
		client_print( plr, print_console, "* You have no access to this command" );
		return PLUGIN_HANDLED;
	}
	
	if( read_argc( ) != 2 )
	{
		client_print( plr, print_console, "Usage: kz_stats_coords ^"<x> <y>^"" );
		return PLUGIN_HANDLED;
	}
	
	new temp[32], x_str[16], y_str[16];
	read_argv( 1, temp, 31 );
	
	new len = 1 + copyc( x_str, 16, temp, ' ' );
	copyc( y_str, 16, temp[len], ' ' );
	
	new Float:x_num, Float:y_num;
	x_num = str_to_float( x_str );
	y_num = str_to_float( y_str );
	
	if( -1.0 <= x_num <= 1.0 && -1.0 <= y_num <= 1.0 )
	{
		g_f__coords[0] = x_num;
		g_f__coords[1] = y_num;
		
		client_print( plr, print_console, "kz_stats_coords changed to ^"%f %f^"", x_num, y_num );
	}
	else
	{
		client_print( plr, print_console, "<x> and <y> have to be between -1.0 and 1.0" );
	}
	
	return PLUGIN_HANDLED;
}

public cmdColor( plr )
{
	if( !( get_user_flags( plr ) & ADMIN_KICK ) )
	{
		client_print( plr, print_console, "* You have no access to this command" );
		return PLUGIN_HANDLED;
	}
	
	if( read_argc( ) != 2 )
	{
		client_print( plr, print_console, "Usage: kz_stats_color ^"<red> <green> <blue>^"" );
		return PLUGIN_HANDLED;
	}
	
	new temp[32], r_str[4], g_str[4], b_str[4];
	read_argv( 1, temp, 31 );
	parse( temp, r_str, 3, g_str, 3, b_str, 3 );
	
	new r_num, g_num, b_num;
	r_num = str_to_num( r_str );
	g_num = str_to_num( g_str );
	b_num = str_to_num( b_str );
	
	if( 0 <= r_num <= 255 && 0 <= g_num <= 255 && 0 <= b_num <= 255 )
	{
		g_i__colors[0] = r_num;
		g_i__colors[1] = g_num;
		g_i__colors[2] = b_num;
		
		client_print( plr, print_console, "kz_stats_color changed to ^"%i %i %i^"", r_num, g_num, b_num );
	}
	else
	{
		client_print( plr, print_console, "<red>, <green> and <blue> have to be between 0 and 255" );
	}	
	
	return PLUGIN_HANDLED;
}

public client_connect( plr )
{
	g_b__lj_stats[plr] = false;
}

Float:get_speed( Float:velocity[3] )
{
	return floatsqroot( floatpower( velocity[0], 2.0 ) + floatpower( velocity[1], 2.0 ) );
}

bool:is_user_ducking( plr )
{
	if( !pev_valid( plr )  )
	{
		return false;
	}
	
	new Float:abs_min[3], Float:abs_max[3];
	
	pev( plr, pev_absmin, abs_min );
	pev( plr, pev_absmax, abs_max );
	
	abs_min[2] += 64.0;
	
	if( abs_min[2] < abs_max[2] )
	{
		return false;
	}
	
	return true;
}
