#include maps\mp\_utility;
#include common_scripts\utility;

CONST_default_radial_radius			= 8; 		// default distance between radial button and center
CONST_radial_center_extrude_dist	= 40; 		// this is the distance between the floating radial center and the oberver's eye
CONST_direct_travel					= 1;		// no path, directly zoom to position
CONST_view_travel_unit_dist			= 1200;		// distance unit per travel interval
CONST_view_travel_unit_time			= 1; 		// in seconds per travel interval
CONST_blur_strength					= 3;		// blur strength during view travel, sine curved over travel duration

init()
{
	precacheShellShock( "frag_grenade_mp" );
	
	radial_button_definitions();				// define radial button sets and buttons
	radial_init();								// setup radial button mechanism
	view_path_setup();							// setup view flight paths
	player_init();
}

// ====================================================================================
// ==                                 		inits 	                                 ==
// ====================================================================================

radial_button_definitions()
{
	newRadialButtonGroup( "main", 				"player_view1_start", 	"player_view1_end" );
	
	//	Main menu's buttons:
		bWeapons_a 	= newRadialButton( "main", 	"Primary Weapon", 		"radial_weapons_primary", 	::action_weapons_primary );
		bWeapons_b	= newRadialButton( "main", 	"Secondary Weapon", 	"radial_weapons_secondary", ::action_weapons_secondary );
		bGears 		= newRadialButton( "main", 	"Gears", 				"radial_gears", 			::action_gears );
		bKillStreaks= newRadialButton( "main", 	"Kill Streaks", 		"radial_killstreaks",		::action_killstreak );
		bLeadboards = newRadialButton( "main", 	"Leaderboards", 		"radial_leaderboards",		::action_leaderboards );
	//
	
	newRadialButtonGroup( "gears", 				"player_view2_start", 	"player_view2_end" );
	newRadialButtonGroup( "weapons_primary", 	"player_view3_start", 	"player_view3_end" );
	newRadialButtonGroup( "weapons_secondary", 	"player_view3_start", 	"player_view3_end" );
	newRadialButtonGroup( "killstreak", 		"player_view4_start", 	"player_view4_end" );
	newRadialButtonGroup( "leaderboards", 		"player_view5_start", 	"player_view5_end" );
}


radial_init()
{
	// calculate start & end angles of all buttons for range selection
	foreach ( button_group in level.radial_button_group )
	{
		// sort buttons by angle so we can calculate mid angles in sequence
		sort_buttons_by_angle( button_group );
		
		for ( i = 0; i < button_group.size; i ++ )
		{
			if ( isdefined( button_group[ i + 1 ] ) )
			{
				mid_angle = getMidAngle( button_group[ i ].pos_angle, button_group[ i + 1 ].pos_angle );
				button_group[ i ].end_angle = mid_angle;
				button_group[ i + 1 ].start_angle = mid_angle;
			}
			else
			{
				mid_angle = getMidAngle( button_group[ i ].pos_angle, button_group[ 0 ].pos_angle ) + 180;	// +180 to mirror angle
				if ( mid_angle > 360 )
					mid_angle -= 360;

				button_group[ i ].end_angle = mid_angle;
				button_group[ 0 ].start_angle = mid_angle;
			}
		}
	}
	
	// monitors
	thread updateSelectedButton();
	thread watchSelectButtonPress();
	thread watchBackButtonPress();
	thread debug_toggle();
}


debug_toggle()
{
	level endon( "game_ended" );
	level.crib_debug = 1;
	
	while ( 1 )
	{
		if ( !isdefined( level.observer ) )
		{
			wait 0.05;
			continue;
		}
		
		button_reset = true;
		while ( !( level.observer buttonPressed( "BUTTON_Y" ) ) )
			wait 0.05;
		
		level.observer playsound("mouse_click");
		
		if ( button_reset )
		{
			level.crib_debug *= -1;
			button_reset = false;
		}
		
		while ( level.observer buttonPressed( "BUTTON_Y" ) )
			wait 0.05;
	}
}


player_init()
{
	level thread onPlayerConnect();
	level thread return_hud();
}


return_hud()
{
	level waittill( "game_ended" );
	setdvar( "cg_draw2d", 1 );	
}


onPlayerConnect()
{
	level waittill("connected", player);
	
	player thread readyPlayer();
	player waittill( "spawned_player" );
	
	wait 1;
	
	player takeallweapons();
	setdvar( "cg_draw2d", 0 );
	
	if ( !isdefined( player ) )
		return;
	else
		level.observer = player;
	
	player thread get_right_stick_angle();	
	
	zoom_to_radial_menu( "main" );	// fly to the first radial menu
}


readyPlayer()
{
	self endon( "disconnect" );
	
	team = "autoassign";

	while(!isdefined(self.pers["team"]))
		wait .05;

	self notify("menuresponse", game["menu_team"], team);
	wait 0.5;

	classes = getArrayKeys( level.classMap );
	okclasses = [];
	for ( i = 0; i < classes.size; i++ )
	{
		if ( !isSubStr( classes[i], "custom" ) )
			okclasses[ okclasses.size ] = classes[i];
	}
	
	assert( okclasses.size );

	while( 1 )
	{
		class = okclasses[ 0 ];
		self notify("menuresponse", "changeclass", class);
			
		self waittill( "spawned_player" );
		wait ( 0.10 );
	}
}

// ====================================================================================
// ==                                Radial Mechanics                                ==
// ====================================================================================

get_right_stick_angle()
{
	// self is user
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	while ( 1 )
	{
		rs_vec = self GetNormalizedMovement();
		rs_angles = vectortoangles( rs_vec );
		level.rs_angle = int( rs_angles[1] );			

		wait 0.05;	// update rate
	}
}


newRadialButtonGroup( group_name, view_start, view_end )
{
	if ( isdefined( level.radial_button_group ) && level.radial_button_group.size )
		assertex( !isdefined( level.radial_button_group[ group_name ] ), "Radial button group: " + group_name + " is already defined." );

	player_view_ent = getent( view_end, "targetname" );
	assertex( isdefined( player_view_ent ), "Missing player view entity, can not setup radial menu in space" );

	extruded_vec = vector_multiply( VectorNormalize( AnglesToForward( player_view_ent.angles ) ), CONST_radial_center_extrude_dist );

	level.radial_button_group[ group_name ] = [];
	level.radial_button_group_info[ group_name ][ "view_start" ] 		= view_start;
	level.radial_button_group_info[ group_name ][ "view_pos" ] 			= player_view_ent.origin + extruded_vec;
	level.radial_button_group_info[ group_name ][ "player_view_pos" ] 	= player_view_ent.origin;
	level.radial_button_group_info[ group_name ][ "view_angles" ] 		= player_view_ent.angles;
}


newRadialButton( button_group, button_label, button_ent_name, action_func )
{
	assertex( isdefined( level.radial_button_group[ button_group ] ), "Radial button group: " + button_group + " does not exist." );
	
	ent = getent( button_ent_name, "targetname" );
	new_button_angle = getRadialAngleFromEnt( button_group, ent );
	
	button 				= spawnstruct();
	button.pos 			= ent.origin;
	button.label 		= button_label;
	button.font_size	= 1;
	button.font_color	= ( 0.5, 0.5, 1 );
	button.pos_angle 	= new_button_angle;
	button.action_func 	= action_func;
	button.radius_pos 	= CONST_default_radial_radius;
	
	level.radial_button_group[ button_group ][ level.radial_button_group[ button_group ].size ] = button;
	return button;
}


updateSelectedButton()
{
	level endon( "game_ended" );
	
	while ( 1 )
	{
		if ( !isdefined( level.radial_button_current_group ) )
		{
			wait 0.05;
			continue;
		}
		
		last_active_button = level.active_button;
		
		foreach ( button in level.radial_button_group[ level.radial_button_current_group ] )
		{
			if ( isInRange( button.start_angle, button.end_angle ) )	
				level.active_button = button;
			else
				button.font_color	= ( 0.5, 0.5, 1 );
		}
		
		if ( isdefined ( level.active_button ) )
		{
			level.active_button.font_color	= ( 1, 1, 0.5 );
		
			if ( isdefined( last_active_button ) && last_active_button != level.active_button )
				level.observer playsound("mouse_over");	
		}
		
		wait 0.05;
	}
}


watchSelectButtonPress()
{
	level endon( "game_ended" );
	
	while ( 1 )
	{
		if ( !isdefined( level.observer ) )
		{
			wait 0.05;
			continue;
		}
		
		button_reset = true;		
		while ( !( level.observer buttonPressed( "BUTTON_A" ) ) )
			wait 0.05;
			
		level.observer playsound("mouse_click");
				
		if ( isdefined( level.active_button ) && button_reset )
		{
			level.active_button notify( "select_button_pressed" );
			[[level.active_button.action_func]]();
			button_reset = false;
		}
		
		while ( level.observer buttonPressed( "BUTTON_A" ) )
			wait 0.05;
	}
}


watchBackButtonPress()
{
	level endon( "game_ended" );
	
	while ( 1 )
	{
		if ( !isdefined( level.observer ) )
		{
			wait 0.05;
			continue;
		}
		
		button_reset = true;
		while ( !( level.observer buttonPressed( "BUTTON_X" ) ) )
			wait 0.05;

		level.observer playsound("mouse_click");
		
		if ( button_reset )
		{
			action_back();
			button_reset = false;
		}
		
		while ( level.observer buttonPressed( "BUTTON_X" ) )
			wait 0.05;
	}
}


sort_buttons_by_angle( button_group )
{
	// button_group is actual array
	// bubble sort buttons
	for ( i = 0; i < button_group.size - 1; i++ )
	{
		for ( j = 0; j < button_group.size - 1 - i; j++ )
		{
			if ( button_group[ j + 1 ].pos_angle < button_group[ j ].pos_angle )
				button_switch( button_group[ j ], button_group[ j + 1 ] );
		}
	}
}


button_switch( button1, button2 )
{
	temp_pos 			= button1.pos;
	temp_label 			= button1.label;
	temp_pos_angle 		= button1.pos_angle;
	temp_action_func 	= button1.action_func;
	temp_radius_pos		= button1.radius_pos;
	
	button1.pos         = button2.pos;        
	button1.label       = button2.label;      
	button1.pos_angle   = button2.pos_angle;  
    button1.action_func = button2.action_func;
    button1.radius_pos  = button2.radius_pos; 
    
 	button2.pos         = temp_pos; 			    
	button2.label       = temp_label; 			
	button2.pos_angle   = temp_pos_angle;		
    button2.action_func = temp_action_func; 	    
    button2.radius_pos  = temp_radius_pos;
}


draw_radial_buttons( button_group )
{
	foreach ( button in level.radial_button_group[ button_group ] )
		button thread draw_radial_button( button_group );
}


//print3d(<origin>, <text>, <color>, <alpha>, <scale>, <duration> )
draw_radial_button( button_group )
{
	level endon( "game_ended" );	
	self endon( "remove_button" );
	
	floating_origin = level.radial_button_group_info[ button_group ][ "view_pos" ];
	button_radial_pos = floating_origin + radial_angle_to_vector( self.pos_angle, 4 );

	while ( 1 )
	{
		//line( level.radial_button_group_info[ button_group ][ "view_pos" ], self.pos, ( 0, 1, 0 ), 0.05, false );
		
		range_color = ( 1, 0, 0 );
		if ( isInRange( self.start_angle, self.end_angle ) )
			range_color = ( 1, 1, 0 );
			
		print3d( self.pos, self.label, self.font_color, 0.75, self.font_size, 1 );
		
		if ( isdefined( level.crib_debug ) && level.crib_debug > 0 )
		{
			print3d( button_radial_pos, ".("+int(self.pos_angle)+")", range_color, 0.75, 0.05, 1 );
	
			line( floating_origin, floating_origin + radial_angle_to_vector( self.start_angle, 2 ), range_color, 0.05 );
			line( floating_origin + radial_angle_to_vector( self.start_angle, 2 ), floating_origin + radial_angle_to_vector( self.end_angle, 2 ), range_color, 0.05 );
	
			// right stick debug ling
			r_radial_pos = floating_origin + radial_angle_to_vector( level.rs_angle, 2 );
			line( floating_origin, r_radial_pos, ( 1, 1, 1 ), 0.05 );
			
			
		}
		print3d( floating_origin - ( 0, 0, 4.5 ), "(A)=Select (X)=Back", (1, 1, 1), 0.5, 0.05, 1 );
		
		wait 0.05;
	}
}


Zoom_To_Radial_Menu( button_group, reverse )
{
	level.active_button = undefined;
	
	assertex( isdefined( level.observer ), "Missing observer (connected player), can not attach player to view path" );
	
	if ( isdefined( level.radial_button_current_group ) && level.radial_button_current_group != "" )
	{
		level.radial_button_previous_group = level.radial_button_current_group;
	}
	else
	{
		level.radial_button_previous_group = "main";
		level.radial_button_current_group = "main";
	}
	
	foreach ( button in level.radial_button_group[ level.radial_button_previous_group ] )
			button notify( "remove_button" );

	//iPrintLnBold( "flying to: " + button_group );
	
	if ( isdefined( reverse ) && reverse )
		level.observer go_path_by_targetname_reverse( level.radial_button_group_info[ level.radial_button_previous_group ][ "view_start" ], button_group );
	else
		level.observer go_path_by_targetname( level.radial_button_group_info[ button_group ][ "view_start" ] );
	
	level thread draw_radial_buttons( button_group );
	level.radial_button_current_group = button_group;
}


// ====================================================================================
// ==                              	Radial menu - math		                         ==
// ====================================================================================

// edit function with care, returns orientation-sensistive angles
getRadialAngleFromEnt( button_group, ent )
{
	assertex( isdefined( level.radial_button_group[ button_group ] ), "getRadialAngleFromEnt: Radial button group does not exist." );
	assertex( isdefined( ent ), "getRadialAngleFromEnt: Missing entity to be measured." );
	
	rAngle 		 = level.radial_button_group_info[ button_group ][ "view_angles" ];
	rPos 		 = level.radial_button_group_info[ button_group ][ "view_pos" ];
	rPos 		+= vector_multiply( VectorNormalize( AnglesToForward( rAngle ) ), CONST_radial_center_extrude_dist );
	rForward 	 = AnglesToForward( rAngle );
	rUpwardNorm  = VectorNormalize( AnglesToUp( rAngle ) );
	
	eAngle 		 = ent.angles;
	ePos 		 = ent.origin;

	projNorm 	 = VectorNormalize( VectorFromLineToPoint( rPos, ( rPos + rForward ), ePos ) );
	radial_angle = Acos( VectorDot( projNorm, rUpwardNorm ) );

	// vector mirroring
	if ( VectorDot( AnglesToRight( rAngle ), projNorm ) < 0 )
		radial_angle = 360 - radial_angle;
		
	return radial_angle;
}


// converts projected angle into player's view plane into a vector
radial_angle_to_vector( angle, scaler )
{
	b_angles 	= ( 270 - ( angle ), 0 , 0 );		// 270 degrees offset to face the player
	b_vec 		= AnglesToForward( b_angles );
	b_vec_norm 	= VectorNormalize( b_vec );
	b_vec_final = vector_multiply( b_vec_norm, scaler );
	
	return b_vec_final;
}


getMidAngle( a1, a2 )
{
	// 0 -> 360 domain
	mid_angle = ( ( a1 + a2 + 720 ) / 2 ) - 360;		
	return mid_angle;
}


isInRange( start_angle, end_angle )
{
	inside_big_angle 	= ( level.rs_angle > start_angle && level.rs_angle < 360 );
	inside_small_angle 	= ( level.rs_angle > 0 && level.rs_angle < end_angle );
	
	if ( start_angle > end_angle )
		in_range = ( inside_big_angle || inside_small_angle );
	else
		in_range = ( level.rs_angle > start_angle && level.rs_angle < end_angle );

	return in_range;
}

// ====================================================================================
// ==                          Button action functions                               ==
// ====================================================================================

// close radial buttons
action_back()
{
	//if ( isdefined( level.radial_button_previous_group ) && level.radial_button_previous_group != "" )
	//	zoom_to_radial_menu( level.radial_button_previous_group );
	/*else*/ if ( isdefined( level.radial_button_current_group ) && level.radial_button_current_group != "main" )
		zoom_to_radial_menu( "main", true );
	else
		return;
}


// ==== main ====
action_weapons_primary()
{
	iPrintLnBold( "action_weapons_primary" );
	zoom_to_radial_menu( "weapons_primary" );
}


action_weapons_secondary()
{
	iPrintLnBold( "action_weapons_secondary" );	
	zoom_to_radial_menu( "weapons_secondary" );
}

action_gears()
{
	iPrintLnBold( "action_gears" );	
	zoom_to_radial_menu( "gears" );
}


action_killstreak()
{
	iPrintLnBold( "action_killstreak" );	
	zoom_to_radial_menu( "killstreak" );
}


action_leaderboards()
{
	iPrintLnBold( "action_leaderboards" );	
	zoom_to_radial_menu( "leaderboards" );
}

// ====================================================================================
// ==                             Pathing functions                                  ==
// ====================================================================================

view_path_setup()
{
	// setup all paths
	level.view_paths = [];
	
	// build paths
	build_path_by_targetname( "player_view1_start" );
	build_path_by_targetname( "player_view2_start" );
	build_path_by_targetname( "player_view3_start" );
	build_path_by_targetname( "player_view4_start" );
	build_path_by_targetname( "player_view5_start" );
}


build_path_by_targetname( path_name )
{
	level.view_paths[ path_name ] = [];
	
	path_node = getent( path_name, "targetname" );
	level.view_paths[ path_name ][ level.view_paths[ path_name ].size ] = path_node;
	
	while( isdefined( path_node ) && isdefined( path_node.target ) )
	{
		next_node = getent( path_node.target, "targetname" );
		level.view_paths[ path_name ][ level.view_paths[ path_name ].size ] = next_node;
		path_node = next_node;
	}
}


go_path_by_targetname( path_name )
{	
	// self is player
	if ( !isdefined( level.dummy_mover ) )
	{
		start_node = level.view_paths[ path_name ][ 0 ];
		level.dummy_mover = spawn( "script_model", start_node.origin );
		level.dummy_mover.angles = start_node.angles;
		//self AllowedStances( "stand" );
		self setOrigin( level.dummy_mover.origin - ( 0, 0, 65 ) );
		self linkTo( level.dummy_mover );
		wait 0.05;
		self setplayerangles ( level.dummy_mover.angles );
		
		self thread force_player_angles();
	}
	
	/*
	travel_time = 1;
	dist = 0;
	foreach ( idx, node in level.view_paths[ path_name ] )
	{
		if ( isdefined( level.view_paths[ path_name ][ idx + 1 ] ) )
			dist += abs( distance( level.view_paths[ path_name ][ idx ].origin, level.view_paths[ path_name ][ idx + 1 ].origin ) );
	}*/
	
	travel_speed = CONST_view_travel_unit_time;
	total_distance = abs( distance( level.dummy_mover.origin, level.view_paths[ path_name ][ level.view_paths[ path_name ].size - 1 ].origin ) );
	travel_speed *= total_distance / CONST_view_travel_unit_dist;
	travel_speed = max( travel_speed, 0.1 );	// due to repeated button presses, the travel distance can be cut to 0 travel speed at times.
	
	blur_time = travel_speed;
	if ( !CONST_direct_travel )
		blur_time *= travel_speed * ( level.view_paths[ path_name ].size + 1 );
	self thread blur_sine( CONST_blur_strength, blur_time );
	
	foreach ( idx, node in level.view_paths[ path_name ] )
	{
		//travel_speed = travel_time * ( abs( distance( level.dummy_mover.origin, node.origin ) ) / dist );
		//travel_speed += 0.05;
		
		if ( CONST_direct_travel )
		{
			if ( idx != level.view_paths[ path_name ].size - 1 )
				continue;
		}
			
		//level.dummy_mover MoveTo( node.origin, travel_speed );
		level.dummy_mover MoveTo( node.origin, travel_speed, travel_speed * 0.5, 0 );
		level.dummy_mover RotateTo( node.angles, travel_speed, travel_speed * 0.5, 0);
		wait travel_speed;
	}
}


go_path_by_targetname_reverse( path_name, back_to_button_group )
{	
	assertex( isdefined( level.dummy_mover ), "go_path_by_targetname_reverse called before go_path_by_targetname" );
	
	travel_speed = CONST_view_travel_unit_time;
	total_distance = abs( distance( level.dummy_mover.origin, level.radial_button_group_info[ back_to_button_group ][ "player_view_pos" ] ) );
	travel_speed *= total_distance / CONST_view_travel_unit_dist;
	travel_speed = max( travel_speed, 0.1 );	// due to repeated button presses, the travel distance can be cut to 0 travel speed at times.
	
	blur_time = travel_speed;
	if ( !CONST_direct_travel )
		blur_time *= travel_speed * ( level.view_paths[ path_name ].size + 1 );
	self thread blur_sine( CONST_blur_strength, blur_time );
		
	if ( !CONST_direct_travel )
	{
		for ( idx = level.view_paths[ path_name ].size - 1; idx >= 0; idx-- )
		{
			node = level.view_paths[ path_name ][ idx ];
			level.dummy_mover MoveTo( node.origin, travel_speed );
			level.dummy_mover RotateTo( node.angles, travel_speed );
	
			//self thread travel_view_fx( travel_speed );
			wait travel_speed;
		}
	}
	
	self thread blur_sine( CONST_blur_strength, travel_speed );
	
	pos = level.radial_button_group_info[ back_to_button_group ][ "player_view_pos" ];
	angle = level.radial_button_group_info[ back_to_button_group ][ "view_angles" ];
	
	level.dummy_mover MoveTo( pos, travel_speed, travel_speed * 0.5, 0 );
	level.dummy_mover RotateTo( angle, travel_speed, travel_speed * 0.5, 0 );
	wait travel_speed;
}


travel_view_fx( time )
{
	self setblurforplayer( 20, ( time + 0.2 )/2 );
	self setblurforplayer( 0, ( time + 0.2 )/2 );		
	self shellshock( "frag_grenade_mp", time + 0.2 );	
}


blur_sine( strength, time )
{
	time_scaled = int( time/0.05 );
	
	for( i = 0; i < time_scaled; i ++ )
	{	
		fraction 	= ( i / ( time_scaled ) );
		cos_fraction= sin( 180 * fraction );
		blur_amount = strength * cos_fraction;
		
		setdvar( "r_blur", blur_amount );
		wait 0.05;
	}
	setdvar( "r_blur", 0 );
}


force_player_angles()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	level.dummy_mover endon( "remove_dummy" );
	
	while ( 1 )
	{
		self setplayerangles ( level.dummy_mover.angles );
		wait 0.05;
	}
}