#include common_scripts\utility;
#include maps\_utility;
#include maps\_debug;
#include maps\_vehicle;
#include maps\_anim;

ANG_LOOKAHEAD_DIST = 800;
AHEAD_DISTANCE = 500;
WHITE = ( 1, 1, 1 );
RED = ( 1, 0, 0 );
GREEN = ( 0, 1, 0 );
BLUE = ( 0, 0, 1 );


init_vehicle_splines()
{
	create_dvar( "vehicle_spline_debug", 0 );
	
	level._SPLINE_MIN_PROGRESS = -2000;
	level._enemy_snowmobiles_max = 6;
	level._player_ent = spawn( "script_origin", level._player.origin + ( 0, 0, 88 ) );
	level._player_ent linkto( level._player );
	level._snowmobile_path = make_road_path();
	flag_init( "ai_snowmobiles_ram_player" );
	flag_set( "ai_snowmobiles_ram_player" );
	
	enable_spline_paths = getentarray( "enable_spline_path", "targetname" );
	array_thread( enable_spline_paths, ::enable_spline_path_think );
}

enable_spline_path_think()
{
	for ( ;; )
	{
		self waittill( "trigger", other );
		other notify( "enable_spline_path" );
	}
}

make_road_path()
{

	level._drive_spline_path_fun = ::bike_drives_path;

	path = process_path();
	flag_init( "race_complete" );
	level._player_view_org = spawn( "script_model", ( 0, 0, 0 ) );
	level._player_view_org setmodel( "tag_origin" );

	level._enemy_snowmobiles = [];
	level._bike_score = 0;
	level._player thread bike_death_score();
	return path;
}

bike_death_score()
{
	self waittill( "death" );
}


get_guy_from_spawner()
{
	spawner = getent( "spawner", "targetname" );
	spawner.count = 1;
	spawner.origin = self.origin;
	spawner.angles = ( 0, self.angles[ 1 ], 0 );
	return spawner stalingradspawn();
}

#using_animtree( "generic_human" );
orient_dir( yaw )
{
	for ( ;; )
	{
		if ( !isdefined( self ) )
			return;
		self OrientMode( "face angle", yaw );
		wait( 0.05 );
	}
}

process_path()
{
	path = create_path();
	level._snowmobile_path = path;
	//drop_path_to_ground( path );
	add_collision_to_path( path );
	/#
	if ( getdebugdvarint( "vehicle_spline_debug" ) )
		thread draw_path( path );
	#/
	return path;
}

droppedLine( start, end, color, depth, cull, timer )
{
	start = drop_to_ground( start );
	end = drop_to_ground( end );
	thread linedraw( start, end, color, depth, cull, timer );
}

droppedLineZ( z, start, end, color, depth, cull, timer )
{
	start = ( start[ 0 ], start[ 1 ], z );
	start = drop_to_ground( start );

	end = ( end[ 0 ], end[ 1 ], z );
	end = drop_to_ground( end );
	thread linedraw( start, end, color, depth, cull, timer );
}

draw_path( path )
{
	old_left_node = undefined;
	old_right_node = undefined;

	for ( i = 0; i < path.size; i++ )
	{
		node = path[ i ];
		angles = vectortoangles( node.next_node.midpoint - node.midpoint );
		forward = anglestoforward( angles ) * node.dist_to_next_targ;

		width = node.road_width * 0.5;
		start1 = get_position_from_spline( node, 0, width );
		end1 = get_position_from_spline( node, node.dist_to_next_targ, width );
		droppedLineZ( node.z, start1, end1, ( 0, 0.5, 1 ), 1, 1, 50000 );

		start2 = get_position_from_spline( node, 0, width * -1 );
		end2 = get_position_from_spline( node, node.dist_to_next_targ, width * -1 );
		droppedLineZ( node.z, start2, end2, ( 0, 0.5, 1 ), 1, 1, 50000 );

		droppedLineZ( node.z, start1, start2, ( 0, 0.5, 1 ), 1, 1, 50000 );
		droppedLineZ( node.z, end1, end2, ( 0, 0.5, 1 ), 1, 1, 50000 );
		


		/*		
		start = node.origins[ "left" ];
		end = start + forward;
		droppedLineZ( node.z, start, end, ( 1, 0.3, 0 ), 1, 1, 50000 );

		start = node.origins[ "right" ];
		end = start + forward;
		droppedLineZ( node.z, start, end, ( 1, 0.3, 0 ), 1, 1, 50000 );

		droppedLineZ( node.z, node.origins[ "left" ] + (1,1,1), node.origins[ "right" ] + (1,1,1), ( 1, 0.3, 0 ), 1, 1, 50000 );

		droppedLineZ( node.z, node.origins[ "left" ] + (1,1,1), node.origins[ "left" ] + (1,1,1) + forward, ( 1, 0.3, 0 ), 1, 1, 50000 );
		droppedLineZ( node.z, node.origins[ "right" ] + (1,1,1), node.origins[ "right" ] + (1,1,1) + forward, ( 1, 0.3, 0 ), 1, 1, 50000 );

		droppedLineZ( node.z, node.origins[ "left" ] + (1,1,1) + forward, node.origins[ "right" ] + (1,1,1) + forward, ( 1, 0.3, 0 ), 1, 1, 50000 );
		*/
		/*		
		midpoint1 = node.midpoint;
		midpoint2 = node.next_node.midpoint;
		
		angles = vectortoangles( midpoint1 - midpoint2 );
		right = anglestoright( angles );
		road_half_width = node.road_width * 0.5;
		left_node = node.midpoint + right * road_half_width;
		right_node = node.midpoint + right * road_half_width * -1;

		droppedLineZ( node.z, left_node, right_node, ( 1, 0.3, 0 ), 1, 0, 50000 );
		if ( isdefined( old_left_node ) )
		{
			droppedLineZ( node.z, left_node, old_left_node, ( 1, 0.3, 0 ), 1, 0, 50000 );
			droppedLineZ( node.z, right_node, old_right_node, ( 1, 0.3, 0 ), 1, 0, 50000 );
		}
		old_left_node = left_node;
		old_right_node = right_node;
		*/

		//Print3d( node.midpoint, "*", ( 0, 1, 1 ), 1, 3, 50000 );
		foreach ( col_volume in node.col_volumes )
		{
			node draw_col_vol( node.z, col_volume );
		}

		foreach ( col_line in node.col_lines )
		{
			//Line( col_line.origin, node.origin );
			//Print3d( col_line.origin, col_line.progress, (0.5,1,1), 1, 2, 50000 );
			start = col_line.origin;
			end = col_line.other_col_point.origin;
			droppedlineZ( node.z, start, end, ( 1, 0, 0 ), 1, 1, 50000 );

			//print3d( col_line.origin + (0,0,-30 ), col_line.offset, ( 1,1,1), 1, 1, 50000 );
			//print3d( col_line.other_col_point.origin + (0,0,-30 ), col_line.other_col_point.offset, ( 1,1,1), 1, 1, 50000 );
		}
	}
}

draw_col_vol( z, vol )
{
	start = get_position_from_spline( self, vol[ "min" ], vol[ "left_offset" ] );
	end = get_position_from_spline( self, vol[ "max" ], vol[ "left_offset" ] );
	droppedLineZ( z, start, end, ( 0.5, 0, 1 ), 1, 1, 50000 );

	start = get_position_from_spline( self, vol[ "min" ], vol[ "right_offset" ] );
	end = get_position_from_spline( self, vol[ "max" ], vol[ "right_offset" ] );
	droppedLineZ( z, start, end, ( 0.5, 0, 1 ), 1, 1, 50000 );

	start = get_position_from_spline( self, vol[ "min" ], vol[ "right_offset" ] );
	end = get_position_from_spline( self, vol[ "min" ], vol[ "left_offset" ] );
	droppedLineZ( z, start, end, ( 0.5, 0, 1 ), 1, 1, 50000 );

	start = get_position_from_spline( self, vol[ "max" ], vol[ "right_offset" ] );
	end = get_position_from_spline( self, vol[ "max" ], vol[ "left_offset" ] );
	droppedLineZ( z, start, end, ( 0.5, 0, 1 ), 1, 1, 50000 );
}

draw_col_vol_offset( Z, vol, offset, forward, right )
{
	targ = self;

	start = get_position_from_spline( targ, vol[ "min" ], vol[ offset ] );
	end = get_position_from_spline( targ, vol[ "max" ], vol[ offset ] );
	droppedLineZ( z, start, end, ( 0.5, 0, 1 ), 1, 1, 50000 );
	


	/*
	angles = vectortoangles( self.next_node.origin - self.origin );
	forward = anglestoforward( angles );
	right = anglestoright( angles );

	start = self.midpoint - right * vol[ "right_offset" ] + forward * vol[ "min" ];
	end = self.midpoint - right * vol[ "right_offset" ] + forward * vol[ "max" ];
	start = drop_to_ground( start );
	end = drop_to_ground( end );
	Line( start, end, ( 1, 1, 1 ), 1, 0, 50000 );

	start = self.midpoint - right * vol[ "left_offset" ] + forward * vol[ "min" ];
	end = self.midpoint - right * vol[ "left_offset" ] + forward * vol[ "max" ];
	start = drop_to_ground( start );
	end = drop_to_ground( end );
	Line( start, end, ( 1, 1, 1 ), 1, 0, 50000 );
	*/
}

create_path()
{
	targ = getstruct( "road_path_left", "targetname" );
	assert( isdefined( targ ) );

	path = [];

	/#
	targ.z = targ.origin[ 2 ];
	#/
	targ.origin = ( targ.origin[ 0 ], targ.origin[ 1 ], 0 );

	count = 0;
	prev_targ = targ;
	for ( ;; )
	{
		next_targ = targ;
		if ( isdefined( targ.target ) )
			next_targ = getstruct( targ.target, "targetname" );

		 /#
		if ( !isdefined( next_targ.z ) )
			next_targ.z = next_targ.origin[ 2 ];
		#/
		next_targ.origin = ( next_targ.origin[ 0 ], next_targ.origin[ 1 ], 0 );

		path[ path.size ] = targ;
		targ.next_node = next_targ;
		targ.prev_node = prev_targ;
		next_targ.previous_node = targ;
		targ.col_lines = [];
		targ.col_volumes = [];
		targ.col_radiuses = [];
		targ.origins = [];
		targ.dist_to_next_targs = [];
		targ.origins[ "left" ] = targ.origin;

		targ.index = count;
		count++ ;

		if ( targ == next_targ )
			break;
		prev_targ = targ;
		targ = next_targ;
	}

	targ = getstruct( "road_path_right", "targetname" );
	/#
	targ.z = targ.origin[ 2 ];
	#/
	targ.origin = ( targ.origin[ 0 ], targ.origin[ 1 ], 0 );

	new_count = 0;
	for ( ;; )
	{
		next_targ = targ;
		if ( isdefined( targ.target ) )
			next_targ = getstruct( targ.target, "targetname" );

/*			
		vehicle_node = undefined;
		if ( isdefined( targ.script_linkto ) )
		{
			vehicle_node = getVehicleNode( targ.script_linkto, "script_linkname" );
		}
*/

		 /#
		if ( !isdefined( next_targ.z ) )
			next_targ.z = next_targ.origin[ 2 ];
		#/
		next_targ.origin = ( next_targ.origin[ 0 ], next_targ.origin[ 1 ], 0 );

		assertex( count >= new_count, "Had more right road nodes than left road nodes." );

		parent = path[ new_count ];
		assertex( isdefined( parent ), "Had more left road nodes than right road nodes." );
		parent.origins[ "right" ] = targ.origin;
		parent.road_width = distance( parent.origins[ "right" ], parent.origins[ "left" ] );
		
/*
		if ( isdefined( vehicle_node ) )
		{
			parent.vehicle_node = vehicle_node;
		}
*/		

		new_count++ ;

		if ( targ == next_targ )
			break;

		targ = next_targ;
	}


	assertex( count == new_count, "Had more left road nodes than right road nodes." );

	foreach ( node in path )
	{
		node.midpoint = ( node.origins[ "left" ] + node.origins[ "right" ] ) * 0.5;
	}

	foreach ( node in path )
	{
		// calc the new left/right based on road width		
		midpoint1 = node.midpoint;
		midpoint2 = node.next_node.midpoint;

		angles = vectortoangles( midpoint1 - midpoint2 );
		right = anglestoright( angles );
		road_half_width = node.road_width * 0.5;
		node.origins[ "left" ] = node.midpoint + right * road_half_width;
		node.origins[ "right" ] = node.midpoint + right * road_half_width * - 1;
	}

	// this node doesn't get a mid point because it has no .target
	node = path[ path.size - 1 ].next_node;
	node.midpoint = ( node.origins[ "left" ] + node.origins[ "right" ] ) * 0.5;

	foreach ( node in path )
	{
		node.dist_to_next_targ = distance( node.midpoint, node.next_node.midpoint );
		node.dist_to_next_targs[ "left" ] = distance( node.origins[ "left" ], node.next_node.origins[ "left" ] );
		node.dist_to_next_targs[ "right" ] = distance( node.origins[ "right" ], node.next_node.origins[ "right" ] );
	}

	return path;
}

drop_path_to_ground( path )
{
	targ = self;
	foreach ( node in path )
	{
		node.origin += ( 0, 0, 20 );
		endpos = PhysicsTrace( node.origin, node.origin + ( 0, 0, -100 ) );
		node.origin = endpos;
	}
}

add_collision_to_path( path )
{
	collision_lines = getstructarray( "moto_line", "targetname" );
	foreach ( collision_line in collision_lines )
	{
		collision_line.origin = ( collision_line.origin[ 0 ], collision_line.origin[ 1 ], 0 );
		next_line = getstruct( collision_line.target, "targetname" );
		// each collision line is made up of two points and each have a refence
		// to the other
		collision_line.other_col_point = next_line;
		next_line.other_col_point = collision_line;
	}

	foreach ( collision_line in collision_lines )
	{
		//droppedLine( collision_line.origin, collision_line.other_col_point.origin, GREEN, 1, 0, 50000 );
	}

	targ = self;
	foreach ( node in path )
	{
		foreach ( collision_line in collision_lines )
		{
			add_collision_to_path_ent( node, collision_line );
		}
	}

	// add the hard collision radiuses to the nearest path points
	moto_collision = getentarray( "moto_collision", "targetname" );
	foreach ( col_radius in moto_collision )
	{
		closest_nodes = get_array_of_closest( col_radius.origin, path, undefined, 2 );
		foreach ( node in closest_nodes )
		{
			node.col_radiuses[ node.col_radiuses.size ] = col_radius;
		}
	}
}

get_offset_percent( targ, next_targ, progress, offset )
{
	// translates an offset distance to a percentage
	dist = distance( targ.midpoint, next_targ.midpoint );
	//assertex( progress <= dist, "Too much progress" );

	progress_percent = 1 - ( progress / dist );

	offset_side = "left";
	if ( offset > 0 )
	{
		offset_side = "right";
	}

	// bumper meaning the outer edge of the current targ area
	bumper_start = targ.origins[ offset_side ];
	bumper_end = next_targ.origins[ offset_side ];

	// the origin equivalent to our progress, on the bumper
	bumper_org = bumper_start * progress_percent + bumper_end * ( 1 - progress_percent );

	center_start = targ.midpoint;
	center_end = next_targ.midpoint;

	// our progress on the center divider
	center_org = center_start * progress_percent + center_end * ( 1 - progress_percent );

	// the track width at our current progress
	track_width = distance( center_org, bumper_org );

	// our offset proportion based on that width
	return offset / track_width;
}

add_collision_to_path_ent( targ, col_org )
{
	if ( targ == targ.next_node )
		return;

	max_dist = targ.road_width;
	if ( targ.dist_to_next_targ > max_dist )
		max_dist = targ.dist_to_next_targ;
	
	if ( distance( col_org.origin, targ.next_node.midpoint ) > max_dist * 1.5 )
		return;

	next_org = getstruct( col_org.target, "targetname" );

	prog1 = get_progression_between_points( col_org.origin, targ.midpoint, targ.next_node.midpoint );
	progress1 = prog1[ "progress" ];

	prog2 = get_progression_between_points( next_org.origin, targ.midpoint, targ.next_node.midpoint );
	progress2 = prog2[ "progress" ];

	if ( progress1 < 0 || progress2 < 0 )
		return;
	if ( progress1 > targ.dist_to_next_targ && progress2 > targ.dist_to_next_targ )
		return;

	assertex( progress1 >= 0, "Negative progress" );
	assertex( progress2 >= 0, "Negative progress" );
	
	col_org.claimed = true;
	next_org.claimed = true;

	//start = get_position_from_spline( targ, prog1["progress"], prog1["offset"] );
	//end = get_position_from_spline( targ, prog2["progress"], prog2["offset"] );
	//droppedLineZ( targ.z, start, end, RED, 1, 0, 50000 );

	col_org.progress = progress1;
	col_org.offset = prog1[ "offset" ];
	col_org.offset_percent = get_offset_percent( targ, targ.next_node, progress1, prog1[ "offset" ] );

	next_org.progress = progress2;
	next_org.offset = prog2[ "offset" ];
	next_org.offset_percent = get_offset_percent( targ, targ.next_node, progress2, prog2[ "offset" ] );

	col_org.origin = ( col_org.origin[ 0 ], col_org.origin[ 1 ], targ.midpoint[ 2 ] + 40 );
	next_org.origin = ( next_org.origin[ 0 ], next_org.origin[ 1 ], targ.midpoint[ 2 ] + 40 );

	// add the collision ents in order of earliest progress then later progress
	if ( progress1 < progress2 )
	{
		add_collision_offsets_to_path_ent( targ, col_org, next_org );
		targ.col_lines[ targ.col_lines.size ] = col_org;
	}
	else
	{
		add_collision_offsets_to_path_ent( targ, next_org, col_org );
		targ.col_lines[ targ.col_lines.size ] = next_org;
	}
}

add_collision_offsets_to_path_ent( targ, close_org, far_org )
{
	// go through the path ents and apply the collision info to each node
	max_progress = far_org.progress + AHEAD_DISTANCE;
	min_progress = close_org.progress - level._DODGE_DISTANCE;

	right_offset = undefined;
	left_offset = undefined;
	right_offset_percent = undefined;
	left_offset_percent = undefined;
	
	if ( far_org.offset > close_org.offset )
	{
		right_offset = far_org.offset;
		left_offset = close_org.offset;
		right_offset_percent = far_org.offset_percent;
		left_offset_percent = close_org.offset_percent;
	}
	else
	{
		right_offset = close_org.offset;
		left_offset = far_org.offset;
		right_offset_percent = close_org.offset_percent;
		left_offset_percent = far_org.offset_percent;
	}

	start_targ = targ;
	start_max_progress = max_progress;
	start_min_progress = min_progress;

	// travel down the path and set collision	
	for ( ;; )
	{
		add_vol_to_node( targ, max_progress, min_progress, right_offset, left_offset, right_offset_percent, left_offset_percent );
		if ( !isdefined( targ.next_node ) )
			break;
		if ( targ.dist_to_next_targ >= max_progress )
			break;

		max_progress -= targ.dist_to_next_targ;
		targ = targ.next_node;
		min_progress = 0;
	}

	targ = start_targ;
	max_progress = start_max_progress;
	min_progress = start_min_progress;

	// travel up the path and set collision	
	for ( ;; )
	{
		if ( !isdefined( targ.previous_node ) )
			break;
		if ( min_progress > 0 )
			break;

		targ = targ.previous_node;
		max_progress = targ.dist_to_next_targ;
		min_progress = targ.dist_to_next_targ + min_progress;
		add_vol_to_node( targ, max_progress, min_progress, right_offset, left_offset, right_offset_percent, left_offset_percent );
	}
}

add_vol_to_node( targ, max_col_progress, min_col_progress, right_offset, left_offset, right_offset_percent, left_offset_percent )
{
	colvol = [];
	colvol[ "max" ] = max_col_progress;
	if ( colvol[ "max" ] > targ.dist_to_next_targ )
		colvol[ "max" ] = targ.dist_to_next_targ;

	colvol[ "min" ] = min_col_progress;
	if ( colvol[ "min" ] < 0 )
		colvol[ "min" ] = 0;

	assert( colvol[ "min" ] < colvol[ "max" ] );

	colvol[ "left_offset" ] = left_offset;
	colvol[ "right_offset" ] = right_offset;
	
	colvol[ "left_offset_percent" ] = left_offset_percent;
	colvol[ "right_offset_percent" ] = right_offset_percent;
	
	colvol[ "mid_offset" ] = ( right_offset + left_offset ) * 0.5;
	colvol[ "mid_offset_percent" ] = ( right_offset_percent + left_offset_percent ) * 0.5;

	targ.col_volumes[ targ.col_volumes.size ] = colvol;
}


/*
=============
///ScriptDocBegin
"Name: get_progression_between_points( <start> , <first_point> , <second_point> )"
"Summary: "
"Module: Entity"
"CallOn: An entity"
"MandatoryArg: <param1>: "
"OptionalArg: <param2>: "
"Example: "
"SPMP: singleplayer"
///ScriptDocEnd
=============
*/
get_progression_between_points( start, first_point, second_point )
{
	first_point = ( first_point[ 0 ], first_point[ 1 ], 0 );
	second_point = ( second_point[ 0 ], second_point[ 1 ], 0 );
	start = ( start[ 0 ], start[ 1 ], 0 );

	prog = [];

	angles = vectortoangles( second_point - first_point );
	forward = anglestoforward( angles );

	end = first_point;
	difference = vectornormalize( end - start );
	dot = vectordot( forward, difference );

	normal = vectorNormalize( second_point - first_point );
	vec = start - first_point;
	progress = vectorDot( vec, normal );


	offset_org = first_point + forward * progress;

	prog[ "progress" ] = progress;
	prog[ "offset" ] = distance( offset_org, start );

	right = anglestoright( angles );
	difference = vectornormalize( offset_org - start );
	dot = vectordot( right, difference );
	prog[ "dot" ] = dot;
	if ( dot > 0 )
		prog[ "offset" ] *= -1;

	return prog;

}

wipe_out( bike )
{
	foreach ( col_radius in self.targ.col_radiuses )
	{
		crashPoint = ( self.origin[ 0 ], self.origin[ 1 ], 0 );

		if ( distance( ( col_radius.origin[ 0 ], col_radius.origin[ 1 ], 0 ), crashPoint ) < col_radius.radius )
			return true;
	}

	if ( bike.health >= 100 )// 0 && bike.rider.health >= 100 )
		return false;

	level._bike_score++ ;

	return true;
}

vehicle_line( bike )
{
	self endon( "death" );
	bike endon( "death" );
	for ( ;; )
	{
		line( self.origin, bike.origin, ( 0.2, 0.8, 0.3 ), 1, 0 );
		wait( 0.05 );
	}
}

spawner_random_team()
{
	waittillframeend;
	if ( !isdefined( self.riders ) )
		return;
	team = "axis";
	if ( cointoss() )
		team = "allies";
	foreach ( guy in self.riders )
	{
		guy.team = team;
	}
}

get_spawn_position( player_targ, my_progress )
{
	// convert the progress/targ to the actual targ/progress
	ent = move_to_correct_segment( player_targ, my_progress );
	progress = ent.progress;
	targ = ent.targ;

	// pick a random track offset then adjust it for obstacles
	half_road_width = targ.road_width * 0.5;
	offset = undefined;
	if ( isdefined( level._player.offset ) )
	{
		random_offset = 500;
		if ( cointoss() )
			random_offset *= -1;
		offset = level._player.offset + random_offset;
	}
	else
	{
		offset = randomfloatrange( half_road_width * -1, half_road_width );
	}
	
	obstacle_array = get_obstacle_dodge_amount( targ, progress, offset );
	if ( isdefined( obstacle_array["dodge"] ) )
		offset = obstacle_array["dodge"];
		
		
	
	// get the point on the spline
	spawn_pos = get_position_from_spline_unlimited( targ, progress, offset );
	array = [];
	array["spawn_pos"] = spawn_pos;
	array["progress"] = progress;
	array["targ"] = targ;
	array["offset"] = offset;
	return array;
}

debug_enemy_vehicles()
{
	/#
	if ( !getdebugdvarint( "vehicle_spline_debug" ) )
		return;
		
	level notify( "stop_debugging_enemy_vehicles" );
	array_thread( level._enemy_snowmobiles, ::debug_enemy_vehicles_line );
	#/
}

debug_enemy_vehicles_line()
{
	self endon( "death" );
	level endon( "stop_debugging_enemy_vehicles" );
	for ( ;; )
	{
		line( self.origin, level._player.origin, ( 1, 0.5, 0 ) );
		wait( 0.05 );
	}	
}

spawn_enemy_bike()
{
	assertex( isdefined( level._enemy_snowmobiles ), "Please add maps\_vehicle_spline::init_vehicle_splines(); to the beginning of your script" );
	
	/#
	debug_enemy_vehicles();
	#/
	
	if ( level._enemy_snowmobiles.size >= level._enemy_snowmobiles_max )
		return;

	player_targ = get_player_targ();
	player_progress = get_player_progress();
	my_direction = "forward";
	
	spawn_array = get_spawn_position( player_targ, player_progress - 1000 - level._POS_LOOKAHEAD_DIST );
	spawn_pos = spawn_array["spawn_pos"];
	player_sees_me_spawn = within_fov( level._player.origin, level._player.angles, spawn_pos, 0 );
	
	if ( player_sees_me_spawn )
	{ 
		// player could see us so try spawning in front of the player and drive backwards
		spawn_array = get_spawn_position( player_targ, player_progress + 1000 );
		spawn_pos = spawn_array["spawn_pos"];
		my_direction = "backward";
		player_sees_me_spawn = within_fov( level._player.origin, level._player.angles, spawn_pos, 0 );
		if ( player_sees_me_spawn )
		{
			return;
		}
	}
	
	// found a safe spawn pos
	spawn_pos = drop_to_ground( spawn_pos );
	
	
	snowmobile_spawner = getent( "snowmobile_spawner", "targetname" );
	assertEx( isdefined( snowmobile_spawner ), "Need a snowmobile spawner with targetname snowmobile_spawner in the level" );
	targ = spawn_array["targ"];

	snowmobile_spawner.origin = spawn_pos;
	
	//snowmobile_spawner.angles = vectortoangles( snowmobile_path_node.next_node.midpoint - snowmobile_path_node.midpoint );
	snowmobile_spawner.angles = vectortoangles( targ.next_node.midpoint - targ.midpoint );
	/*
	if ( isalive( level.player ) && isdefined( level.player.vehicle ) )
		snowmobile_spawner.angles = level.player.vehicle.angles;
	*/
	
	ai_spawners = snowmobile_spawner get_vehicle_ai_spawners();
	foreach ( spawner in ai_spawners )
	{
		spawner.origin = snowmobile_spawner.origin;
	}

	bike = vehicle_spawn( snowmobile_spawner );
	bike.offset_percent = spawn_array["offset"];
	bike VehPhys_SetSpeed( 90 );
	
	bike thread crash_detection();
	bike.left_spline_path_time = gettime() - 3000;
	waittillframeend; // for bike.riders to get defined
	if ( !isalive( bike ) )
		return;
		
	targ bike_drives_path( bike );
}

crash_detection()
{
	self waittill( "veh_collision", velocity, collisionNormal );
	
	self wipeout( "collision!" );	
}

rider_death_detection( bike )
{
	self waittill( "death" );
	if ( isdefined( bike ) )
	{
		bike wipeout( "driver died!" );
	}
}

wipeout( msg )
{
	/#
	if ( !self.wipeout )
	{
		if ( getdebugdvarint( "vehicle_spline_debug" ) )
			Print3d( self.origin, msg, (1,0.25,0), 1, 1.5, 400 );
	}
	#/
	self.wipeout = true;
}

update_bike_player_avoidance( my_bike )
{
	bikes = [];
	foreach ( bike in level._enemy_snowmobiles )
	{
		if ( !isalive( bike ) )
			continue;
			
		if ( bike.wipeout )
			continue;

		bikes[ bikes.size ] = bike;		
	}
	level._enemy_snowmobiles = bikes;

	if ( isalive( my_bike ) && !my_bike.wipeout )
	{
		found_bike = false;
		foreach ( bike in level._enemy_snowmobiles )
		{
			if ( bike == my_bike )
			{
				found_bike = true;
				continue;
			}
		}
		
		if ( !found_bike )
		{
			level._enemy_snowmobiles[ level._enemy_snowmobiles.size ] = my_bike;
		}
	}

	offset = 0;
	foreach ( bike in level._enemy_snowmobiles )
	{
		bike.bike_avoidance_offset = offset;
		offset += 75;
	}
}

bike_drives_path( bike )
{
	if ( !isdefined( bike.left_spline_path_time ) )
		bike.left_spline_path_time = gettime();
		
	bike.wipeout = false;
	update_bike_player_avoidance( bike );

	// speed = randomfloatrange( 50, 70 );

	if ( !isdefined( bike.player_offset ) )
		bike.player_offset = 250;
		
	bike.steering = 0;
	offset = randomfloatrange( 0, 1 );
	if ( !isdefined( bike.offset_percent ) )
		bike.offset_percent = offset * 2 - 1;

	targ = self;
	ent = spawnstruct();
	ent.origin = self.midpoint;
	ent.progress = 0;
	ent.tilt_vel = 0;
	ent.speed = 100;
	ent ent_flag_init( "biker_reaches_path_end" );
	
	// let other scripts know that track behavior has taken over
	bike notify( "enable_spline_path" );

	/*
	foreach ( rider in bike.riders )
	{
		if ( !isdefined( rider.magic_bullet_shield ) )
			rider thread magic_bullet_shield();
	}
	*/
	
	if ( !bike.riders.size )
	{
		bike VehPhys_Crash();
		return;
	}
	
	array_thread( bike.riders, ::rider_death_detection, bike );

	ent.bike = bike;
	//bike EnableAimAssist();
	//bike playloopsound( "veh_motorcycle_dist_loop" );

	bike.health = 100;

	wipeout = false;
	ent thread bike_ent_wipe_out_check( bike );

	// track the bike's progress along the path so we can do proper angles
	bike.progress_targ = targ;
	bike.offset_modifier = 0;
	bike.fails = 0;
	bike.direction = "forward";
	
	bike.old_pos = bike.origin;

	for ( ;; )
	{
		/#
		if ( getdebugdvarint( "vehicle_spline_debug" ) )
			bike debug_bike_line();
		#/
		
		if ( !isalive( bike ) )
			break;

		set_bike_position( ent );

		if ( !isalive( bike ) )
			break;
		if ( abs( bike.progress_dif ) > 6000 && gettime() > bike.left_spline_path_time + 4000 )
		{
			bike wipeout( "left behind!" );
		}

		waittillframeend;// for bike_ent wipeout to occur
		if ( bike.wipeout )
		{
			
			if ( isdefined( bike.hero ) )
				continue;

			bike VehPhys_Crash();
			foreach ( rider in bike.riders )
			{
				if ( isalive( rider ) )
				{
					//rider stop_magic_bullet_shield();
					rider kill();
				}
			}
			wait( 5 );
			if ( isdefined( bike ) )
			{
				bike delete();
			}

			update_bike_player_avoidance();
			return;
//			bike thread bike_wipes_out( ent );
//			bike hide();
		}

		if ( ent ent_flag( "biker_reaches_path_end" ) || flag( "race_complete" ) )
			break;
	}

	update_bike_player_avoidance();
	
	ent notify( "stop_bike" );
	level notify( "biker_dies" );
	if ( bike.wipeout && !flag( "race_complete" ) )
		wait( 5 );

	ent ent_flag_clear( "biker_reaches_path_end" );

}


get_obstacle_dodge_amount( targ, progress, offset )
{
	array[ "near_obstacle" ] = false;
	foreach ( vol in targ.col_volumes )
	{
		if ( progress < vol[ "min" ] )
			continue;
		if ( progress > vol[ "max" ] )
			continue;
		array[ "near_obstacle" ] = true;
		if ( offset < vol[ "left_offset" ] )
			continue;
		if ( offset > vol[ "right_offset" ] )
			continue;

		org = ( targ.midpoint + targ.next_node.midpoint ) * 0.5;
		//droppedLineZ( targ.z, org, origin, RED, 1, 0, 1 );
		if ( offset > vol[ "mid_offset" ] )
			array[ "dodge" ] = vol[ "right_offset" ];
		else
			array[ "dodge" ] = vol[ "left_offset" ];
		break;
	}
	return array;
}

sweep_tells_vehicles_to_get_off_path()
{
	for ( ;; )
	{
		self waittill( "trigger", other );
		if ( !isdefined( other.script_noteworthy ) )
			continue;
		if ( other.script_noteworthy != "sweepable" )
			continue;
		timer = randomfloatrange( 0, 1 );
		other thread notify_delay( "enable_spline_path", timer );
	}
}

drawmyoff()
{
	for ( ;; )
	{
		if ( isdefined( level._player.vehicle ) )
		{
			my_speed = self vehicle_getSpeed();
			p_speed = level._player.vehicle vehicle_getSpeed();
			Print3d( self.origin + (0,0,64), my_speed - p_speed, (1,0,0.2), 1, 1.2 );
			level._difference = my_speed - p_speed;
			
			Line( self.origin, level._player.origin, (1,0,0.2) );
		}
		wait( 0.05 );
	}
}

priceliner()
{
	/#
	create_dvar( "price_line", 0 );
	for ( ;; )
	{
		if ( !isdefined( level._player.vehicle ) )
			return;
		
		forward = anglestoforward( level._player.vehicle.angles );
		forward *= -150;
		if ( getdebugdvarint( "price_line" ) )
			Line( level._player.origin + forward, self.origin, (1,0,0) );
		wait( 0.05 );
	}
	#/
}

modulate_speed_based_on_progress()
{
	/*
	if ( flag( "price_leaves_player_throttling" ) )
		return;
	level endon( "price_leaves_player_throttling" );
	*/
	
	// thread drawmyoff();
	thread priceliner();
	self.targ = get_my_spline_node( self.origin );
	self.min_speed = 1;
	
	self endon( "stop_modulating_speed" );

	hud = undefined;
	/#
	hud = maps\_hud_util::createFontString( "default", 1.5 );
	hud maps\_hud_util::setPoint( "MIDDLE", "MIDDLE", 0, 30 );
	hud.color = ( 1, 1, 1 );
	hud.alpha = 1;
	#/
	
	
	for ( ;; )
	{
//		wait( randomfloatrange( 0.4, 1.2 ) );
		wait( 0.05 );

		targ = self.targ;
		if ( targ == targ.next_node )
		{
			// reached end
			return;
		}

		array = get_progression_between_points( self.origin, self.targ.midpoint, self.targ.next_node.midpoint );
		progress = array["progress"];
		progress += level._POS_LOOKAHEAD_DIST;

		ent = move_to_correct_segment( self.targ, progress );
		progress = ent.progress;
		self.targ = ent.targ;
		self.progress = progress;

		player_targ = get_player_targ();
		player_progress = get_player_progress();
		dif = progress_dif( self.targ, self.progress, player_targ, player_progress );
		level._progress_dif = dif;
	
		
		
		/*
		if ( dif < -1000 )
		{
			// catch up
			if ( isdefined( player_targ.next_node ) && isdefined( player_targ.next_node.vehicle_node ) )
			{
				path = player_targ.next_node.vehicle_node;
				endpos = self get_bike_pos_from_spline( player_targ, player_progress - 500, 0, path.origin[ 2 ] );
				self Vehicle_Teleport( endpos, self.angles );
				self startPath( path );
				Line( level.player.origin, endpos, (1,0,1), 1, 0, 500 );
			}
		}
		*/

		if ( !isdefined( level._player.vehicle ) )
		{
			self Vehicle_SetSpeed( 65, 1, 1 );
			continue;
		}
			
		if ( abs( dif > 3500 ) )
		{
			speed = 65;
	
			// if we're between these two speeds, then 
			//normal_max = 600; 
			//normal_min = 300;
			//dif_min = 300;
			
			dif *= -1;
			dif += 750; // go this far ahead of the player
			speed = level._player.vehicle.veh_speed + dif * 0.05;
			max_speed = level._player.vehicle.veh_speed;
			if ( max_speed < 100 )
				max_speed = 100;
				
			if ( speed > max_speed )
				speed = max_speed;
			else
			if ( speed < self.min_speed )
				speed = self.min_speed;
			level._desired_speed = speed;
			
			//my_speed = self vehicle_getSpeed();
			//if ( abs( player_speed - my_speed ) > 50 )
			//	self Vehicle_SetSpeedImmediate( speed, 90, 20 );
			//else
			self Vehicle_SetSpeed( speed, 90, 20 );
		}
		else
		{
			price_match_player_speed( 10, 10 );
			/#
				//hud setText( int( array["progress"] ) + " mult:" + multiplier + " dspeed:" + int( my_speed ) + " aspeed:" + int( self.veh_speed ) );
			#/
		}
							
	}
}

price_match_player_speed( maxaccell, maxdecel )
{
	
	angles = self.angles;
	angles = ( 0, angles[1], 0 );
	forward = anglestoforward( angles );
	
	array = get_progression_between_points( level._player.vehicle.origin, self.origin + forward * 1, self.origin - forward * 1 );
	progress = array["progress"];
	
	
	if ( progress > 4000 )
		self Vehicle_SetSpeed( 0, 90, 20 );
	else
	{
		dot = get_dot( self.origin, self.angles, level._player.origin );
		multiplier = 1;
		
		if ( progress > 0 )
		{
			// we're ahead of the player
			
			/*
			if ( progress > 3000 )
				multiplier = 0.1;
			else
			if ( progress > 1000 )
				multiplier = 0.2;
			else
			if ( progress > 500 )
				multiplier = 0.6;
			else
			if ( progress > 300 )
				multiplier = 0.9;
			else
			if ( progress > 150 )
				multiplier = 0.98;
			else
			if ( progress > 100 )
				multiplier = 0.99;
			else
			if ( progress > 50 )
				multiplier = 1.0;
				
			multiplier += randomfloatrange( -0.5, 0.5 );

			if ( multiplier < 1.2 && dot < -0.97 )
			{
				// player is behind us so we need to stay in front of him
				multiplier = 1.2;
			}
			*/
			
			multiplier = 1;
		}
		else
		{
			if ( progress > -500 )
				multiplier = 1.25;
				
			if ( multiplier > 0.95 && dot > 0.97 )
			{
				// player is ahead of us, don't run him over
				multiplier = 0.95;
			}
		}
			
		my_speed = 70 * multiplier;
		if ( my_speed < self.min_speed )
			my_speed = self.min_speed;
		if ( my_speed < 25 )
			my_speed = 25;
	
		/*
		if ( my_speed + 8 < self.veh_speed )
		{
			self Vehicle_SetSpeedImmediate( my_speed, 90, 90 );
		}
		else
		{
			if ( my_speed > self.veh_speed + 50 )
				self Vehicle_SetSpeedImmediate( my_speed, 90, 90 );
			else
				self Vehicle_SetSpeed( my_speed, 90, 90 );
		}
		*/
		
		level._price_desired_speed = my_speed;
		self Vehicle_SetSpeed( my_speed, maxaccell, maxdecel );
	}
}

match_player_speed( maxaccell, maxdecel )
{
	
	angles = self.angles;
	angles = ( 0, angles[1], 0 );
	forward = anglestoforward( angles );
	
	array = get_progression_between_points( level._player.vehicle.origin, self.origin + forward * 1, self.origin - forward * 1 );
	progress = array["progress"];
	
	if ( progress > 4000 )
		self Vehicle_SetSpeed( 0, 90, 20 );
	else
	{
		if ( progress < level._SPLINE_MIN_PROGRESS && gettime() > self.left_spline_path_time + 4000 )
		{
			self wipeout( "low progress!" );
		}
	
		progress -= 750;
		progress += self.bike_avoidance_offset;
		
		multiplier = 1;
	
		if ( progress > 150 )
			multiplier = 0.6;
		else
		if ( progress > 100 )
			multiplier = 1.0;
		else
		if ( progress < -100 )
			multiplier = 1.5;

		if ( isdefined( level._player.offset ) )
		{
			if ( progress > 250 )
			{
				// we're speeding up from behind so stay to the side
//				range = 200;
//				if ( array[ "offset" ] > level.player.offset + range )
//					self.preferred_offset = level.player.offset + range;
//				else
//				if ( array[ "offset" ] < level.player.offset - range )
//					self.preferred_offset = level.player.offset - range;
////				self.preferred_offset = undefined;
			}
			/*
			else
			{
				self.preferred_offset = level.player.offset + randomfloatrange( -150, 150 );
			}
			*/
		}
			
			
		my_speed = level._player.vehicle.veh_speed * multiplier;
		if ( my_speed < 25 )
			my_speed = 25;
	
		/*
		if ( my_speed + 8 < self.veh_speed )
		{
			self Vehicle_SetSpeedImmediate( my_speed, 90, 90 );
		}
		else
		{
			if ( my_speed > self.veh_speed + 50 )
				self Vehicle_SetSpeedImmediate( my_speed, 90, 90 );
			else
				self Vehicle_SetSpeed( my_speed, 90, 90 );
		}
		*/
		
		self Vehicle_SetSpeed( my_speed, maxaccell, maxdecel );
	}
}

track_player_progress( org )
{
	self notify( "track_player_progress" );
	self endon( "track_player_progress" );
	
	self.targ = get_my_spline_node( org );
	self.progress = 0;
	player_sweep_trigger = getent( "player_sweep_trigger", "targetname" );
	sweep_trigger = isdefined( player_sweep_trigger );
	if ( sweep_trigger )
		player_sweep_trigger thread sweep_tells_vehicles_to_get_off_path();
	 
	for ( ;; )
	{
		if ( self.targ == self.targ.next_node )
		{
			// reached end
			return;
		}

		array = get_progression_between_points( self.origin, self.targ.midpoint, self.targ.next_node.midpoint );
		
		progress  = array["progress"];
		progress += level._POS_LOOKAHEAD_DIST;

		ent = move_to_correct_segment( self.targ, progress );
		progress = ent.progress;
		self.targ = ent.targ;
		self.progress = progress;
		self.offset = array[ "offset" ];

		if ( sweep_trigger )
		{
			trigger_pos = get_position_from_spline_unlimited( self.targ, progress + 2000, 0 );
			trigger_pos = ( trigger_pos[ 0 ], trigger_pos[ 1 ], self.origin[ 2 ] - 500 );
			player_sweep_trigger.origin = trigger_pos;
			lookahead_pos = get_position_from_spline_unlimited( self.targ, progress + 3000, 0 );
			angles = vectortoangles( player_sweep_trigger.origin - lookahead_pos );
			player_sweep_trigger.angles = ( 0, angles[ 1 ], 0 );
		}
		
		if ( flag( "ai_snowmobiles_ram_player" ) )
		{
			// now find which enemy is closest to the player
			level._closest_enemy_snowmobile_to_player = getClosest( self.origin, level._enemy_snowmobiles );
		}
		else
		{
			// dont chase the player during crzy downhill part
			level._closest_enemy_snowmobile_to_player = undefined;
		}
				
		
		/*
		sweep_targ = self.targ;
		progress += 2000;
		for ( ;; )
		{
			if ( progress > sweep_targ.dist_to_next_targ )
			{
				progress -= sweep_targ.dist_to_next_targ;
				sweep_targ = sweep_targ.next_node;
				continue;
			}
			break;
		}
		player_sweep_trigger.origin = get_position_from_spline( sweep_targ, progress, 0 );
		player_sweep_trigger.origin = ( player_sweep_trigger.origin[ 0 ], player_sweep_trigger.origin[ 1 ], self.origin[ 2 ] - 500 );

				
		
		sweep_yaw = vectortoangles( sweep_targ.origin - sweep_targ.next_node.origin )[ 1 ];
		sweep_yaw_next = vectortoangles( sweep_targ.next_node.origin - sweep_targ.next_node.next_node.origin )[ 1 ];
		
		yaw = sweep_yaw * progress_percent + sweep_yaw_next * ( 1 - progress_percent );
		player_sweep_trigger.angles = ( 0, yaw, 0 );
		*/
		
		wait( 0.05 );
	}
}

progress_dif( targ, progress, targ2, progress2 )
{
	while ( targ.index > targ2.index )
	{
		targ = targ.prev_node;
		progress += targ.dist_to_next_targ;
	}
	while ( targ2.index > targ.index )
	{
		targ2 = targ2.prev_node;
		progress2 += targ2.dist_to_next_targ;
	}
	
	return progress - progress2;
}


set_bike_position( ent )
{
	bike = ent.bike;
	timer = 0.1;

	// find the bike's progress and position on the path that runs down the middle of the road
	progress = 0;
	offset = 0;

	targ = bike.progress_targ;
	if ( targ == targ.next_node )
	{
		bike delete();
		return;
	}

	// don't consider targs we're a long way away from
	array = get_progression_between_points( bike.origin, targ.midpoint, targ.next_node.midpoint );
	array_next = get_progression_between_points( bike.origin, targ.next_node.midpoint, targ.next_node.next_node.midpoint );
	
	// if we could be in the next nodeset then put us there.
	if ( array_next["progress"] > 0 && array_next["progress"] < targ.next_node.dist_to_next_targ )
	{
		array = array_next;
		targ = targ.next_node;
	}
	offset = array["offset"];
	player_progress = 0;
	progress = array["progress"];
	bike.progress = progress;
	
	obstacle_array = get_obstacle_dodge_amount( targ, progress, offset );
	crashing = obstacle_array["near_obstacle"];
		
	dif = progress_dif( targ, progress, get_player_targ(), get_player_progress() );
	bike.progress_dif = dif;
	if ( bike.direction == "forward" )
	{
		progress += level._POS_LOOKAHEAD_DIST;
	}
	else
	{
		progress -= level._POS_LOOKAHEAD_DIST;
		if ( dif < 500 )
		{
			bike.direction = "forward";
		}
	}

	min_speed = 60; // slowest to go when too fast ahead
	max_speed = 90; // max speed to catch up
	min_dist = 100; // distance at which to go max speed
	max_dist = 200; // distance at which you go slowest speed
	
	if ( dif > max_dist )
	{
		speed = min_speed;
	}
	else
	if ( dif < min_dist )
	{
		speed = max_speed;
	}
	else
	{
		dist_dif = max_dist - min_dist;
		speed_dif = max_speed - min_speed;
		speed = dif - min_dist;
		speed = dist_dif - speed;
		
		speed *= speed_dif / dist_dif;
		speed += min_speed;
		assert( speed >= min_speed && speed <= max_speed );
	}

	if ( speed > 0 )
	{
		if ( bike vehicle_getspeed() < 2 )
		{
			bike.fails++ ;
			if ( bike.fails > 10 )
			{
				bike wipeout( "move fail!" );
				return;
			}
		}
		else
			bike.fails = 0;
	}
	else
		bike.fails = 0;

	
	offset_modifier = randomfloatrange( 0, 100 );
	offset_modifier *= 0.001;
	
	chaseCam = false;
	
	/#
	
	if ( getdvarint( "chasecam" ) )
	{
		chasecam = true;
	}
	#/
	
	current_road_width = targ.road_width;

	ent = move_to_correct_segment( targ, progress );
	progress = ent.progress;
	targ = ent.targ;	
	
	org = ( targ.midpoint + targ.next_node.midpoint ) * 0.5;
	//droppedLineZ( targ.z, org, bike.origin, GREEN, 1, 0, timer );

	// scale the offset by the ratio of the road width I'm going to	
	offset = offset * targ.road_width / current_road_width;
	
	obstacle_array = get_obstacle_dodge_amount( targ, progress, offset );

	if ( isdefined( obstacle_array["dodge"] ) )
	{
		// is there an obstacle? dodge it
		offset = obstacle_array["dodge"];
	}
	else
	{
		if ( isdefined( bike.preferred_offset ) )
		{
			offset = bike.preferred_offset;
		}
	}

	// cap the offset to the legit road
	offset_limit = 0.95;
	road_half_width = targ.road_width * 0.5;
	road_half_width -= 50; // bring the edge in a little
	if ( offset > road_half_width )
		offset = road_half_width;
	else
	if ( offset < - 1 * road_half_width )
		offset = -1 * road_half_width;

	//speed = 70;
	if ( targ != targ.next_node )
	{	
		endpos = bike get_bike_pos_from_spline( targ, progress, offset, bike.origin[ 2 ] );
		
		dot = get_dot( bike.origin, bike.angles, endpos );
		//Print3d( bike.origin, dot, (0,0.3,1), 1, 1, int( timer * 20 ) );
		
		if ( dot < 0.97 )
			speed = 50;
		else
		if ( dot < 0.96 )
			speed = 25;
		else
		if ( dot < 0.95 )
			speed = 15;
		
		bike vehicleDriveTo( endpos, speed );
		if ( !isdefined( level._player.vehicle ) )
		{
			bike Vehicle_SetSpeed( 65, 1, 1 );
		}
		else
		{
			bike.veh_topspeed = level._player.vehicle.veh_topspeed * 1.3;
			bike match_player_speed( 45, 30 );
		}
		
		/#
		if ( getdebugdvarint( "vehicle_spline_debug" ) && isdefined( level._player.vehicle ) )
		
			if ( bike.veh_speed > level._player.vehicle.veh_speed )
				thread Linedraw( bike.origin, endpos, ( 0.9, 0.1, 0.3 ), 1, 0, timer );
			else
				thread Linedraw( bike.origin, endpos, ( 0.3, 0.1, 0.9 ), 1, 0, timer );
		#/
	}
	

	bike.progress_targ = targ;
	bike.offset = offset;
	wait( timer );
}

get_bike_pos_from_spline( targ, progress, offset, z )
{
	bike_lookahead_pos = get_position_from_spline( targ, progress, offset );
	bike_lookahead_pos = set_z( bike_lookahead_pos, z );

	return PhysicsTrace( bike_lookahead_pos + ( 0, 0, 200 ), bike_lookahead_pos + ( 0, 0, -200 ) );
}


move_to_correct_segment( targ, progress )
{
	ent = spawnstruct();
	
	// convert progress to proper progress and targ	
	for ( ;; )
	{
		if ( targ == targ.next_node )
		{
			break;
		}

		if ( progress > targ.dist_to_next_targ )
		{
			progress -= targ.dist_to_next_targ;
			targ = targ.next_node;
			continue;
		}

		if ( progress < 0 )
		{
			progress += targ.dist_to_next_targ;
			targ = targ.prev_node;
			continue;
		}
		
		break;
	}
	
	ent.targ = targ;
	ent.progress = progress;
	return ent;
}


get_position_from_spline_unlimited( targ, progress, offset )
{
	// travels down the path first
	for ( ;; )
	{
		if ( targ == targ.next_node )
		{
			return targ.midpoint;
		}
		
		if ( progress > targ.dist_to_next_targ )
		{
			progress -= targ.dist_to_next_targ;
			targ = targ.next_node;
			continue;
		}
		break;
	}

	return get_position_from_spline( targ, progress, offset );
}



//get_position_from_spline( targ, progress, offset )
//{

	/*
	angles = vectortoangles( targ.midpoint - targ.next_node.midpoint );
	right = anglestoright( angles );
	*/
	//pos = get_position_from_progress( targ, progress );
	//pos = get_position_from_progress( targ, progress );
	
	/*
	new_pos = get_position_from_offset( targ, targ.next_node, progress, offset );

	ratio = 1 - ( progress / targ.dist_to_next_targ );
	droppedLine( targ.midpoint, targ.next_node.midpoint, ( 0.5, 0.5, 0.5 ), 1, 0, 0.2 );
	mid_origin = targ.midpoint * ratio + targ.next_node.midpoint * ( 1 - ratio );
	droppedLine( mid_origin, new_pos, ( 0.4, 0.4, 0.6 ), 1, 0, 0.2 );

	return new_pos;
	*/
//}

get_position_from_spline( targ, progress, offset )
{
	angles = vectortoangles( targ.next_node.midpoint - targ.midpoint );
	forward = anglesToForward( angles );
	right = anglesToRight( angles );
	return targ.midpoint + forward * progress + right * offset;
	
	/*
//	assertex( progress >= 0, "Negative progress" );
	// translates an offset distance to a percentage
	dist = distance( targ.midpoint, next_targ.midpoint );
	assertex( progress <= dist, "Too much progress" );

	progress_percent = 1 - ( progress / targ.dist_to_next_targ );

	offset_side = "left";
	if ( offset < 0 )
	{
		offset_side = "right";
	}

	// bumper meaning the outer edge of the current targ area
	bumper_start = targ.origins[ offset_side ];
	bumper_end = next_targ.origins[ offset_side ];

	// the origin equivalent to our progress, on the bumper
	bumper_org = bumper_start * progress_percent + bumper_end * ( 1 - progress_percent );

	center_start = targ.midpoint;
	center_end = next_targ.midpoint;

	// our progress on the center divider
	center_org = center_start * progress_percent + center_end * ( 1 - progress_percent );

	//droppedLine( center_org, bumper_org, GREEN, 1, 0, 5000 );
	offset_percent = 1 - abs( offset_percent );
	return center_org * offset_percent + bumper_org * ( 1 - offset_percent );
	*/
}


get_position_from_progress( targ, progress )
{
	progress_percent = 1 - ( progress / targ.dist_to_next_targ );
	return targ.midpoint * progress_percent + targ.next_node.midpoint * ( 1 - progress_percent );
}


bike_ent_wipe_out_check( bike )
{
	self endon( "stop_bike" );
	for ( ;; )
	{
		//self.wipeout = wipe_out( bike );
		self.wipeout = false;// need to find out from c0de

		if ( self.wipeout )
			break;
		wait( 0.05 );
	}
}

draw_bike_debug()
{
	for ( ;; )
	{
		waittillframeend;
		Print3d( self.origin, self.goal_dir, ( 1, 1, 1 ), 2 );
		wait( 0.05 );
	}
}

track_progress()
{
	self endon( "stop_bike" );
	for ( ;; )
	{
		start = ( self.origin[ 0 ], self.origin[ 1 ], 0 );
		end = ( self.targ.midpoint[ 0 ], self.targ.midpoint[ 1 ], 0 );
		next_targ = ( self.next_targ.midpoint[ 0 ], self.next_targ.midpoint[ 1 ], 0 );

		difference = vectornormalize( end - start );
		forward = anglestoforward( self.angles );
		dot = vectordot( forward, difference );

		normal = vectorNormalize( next_targ - end );
		vec = start - end;
		self.progress = vectorDot( vec, normal );
		//Print3d( self.origin +(0,0,60), progress );
		wait( 0.05 );
	}
}


set_road_offset( targ )
{
	self.right_offset = targ.road_width * 0.5;
	self.safe_offset = self.right_offset - 100;
}

bike_avoids_obstacles( bike )
{
	self endon( "stop_bike" );
	self endon( "end_path" );

	self.goal_dir = 0;
	//thread bike_assigns_dodge_dir_from_obstacles( bike );
	thread bike_randomly_changes_lanes();
	bike_turns();
}

bike_randomly_changes_lanes()
{
	self endon( "stop_bike" );
	self endon( "end_path" );
	for ( ;; )
	{
		if ( self.targ.col_volumes.size == 0 && self.dodge_dir == 0 )
		{
			if ( cointoss() )
				self.goal_dir++ ;
			else
				self.goal_dir -- ;

			if ( self.goal_dir > 1 )
				self.goal_dir -= 3;
			else
			if ( self.goal_dir < - 1 )
				self.goal_dir += 3;
		}
		wait( randomfloatrange( 1, 3 ) );
	}
}

should_stabilize()
{
	if ( self.goal_dir == 0 )
		return true;

	if ( self.goal_dir == 1 && self.offset > self.safe_offset )
		return true;

	if ( self.goal_dir == -1 && self.offset < self.safe_offset * - 1 )
		return true;

	return false;
}

bike_turns()
{
	self.tilt_vel = 0;
	tilt_vel_max = 12;
	tilt_rate = 3;
	max_turn_speed = 130;
	for ( ;; )
	{
		if ( should_stabilize() )
		{
			// stabilizing
			if ( self.tilt > 0 )
			{
				self.tilt_vel -= tilt_rate;
			}
			else
			if ( self.tilt < 0 )
			{
				self.tilt_vel += tilt_rate;
			}

		}
		else
		if ( self.goal_dir == 1 )
		{
			self.tilt_vel += tilt_rate;
		}
		else
		if ( self.goal_dir == -1 )
		{
			self.tilt_vel -= tilt_rate;
		}

		if ( self.tilt_vel > tilt_vel_max )
		{
			self.tilt_vel = tilt_vel_max;
		}
		else
		if ( self.tilt_vel < - 1 * tilt_vel_max )
		{
			self.tilt_vel = -1 * tilt_vel_max;
		}

		self.tilt += self.tilt_vel;
		if ( self.tilt > max_turn_speed )
		{
			self.tilt = max_turn_speed;
			self.tilt_vel = 1;// keep some tilt so we don't change the turn logic
		}
		else
		if ( self.tilt < max_turn_speed * - 1 )
		{
			self.tilt = max_turn_speed * - 1;
			self.tilt_vel = -1;// keep some tilt so we don't change the turn logic
		}

		wait( 0.05 );
	}
}

stabalize( max_turn_speed, tilt_rate )
{
	if ( self.tilt > 0 )
	{
		self.tilt -= tilt_rate;
	}
	else
	{
		self.tilt += tilt_rate;
	}

	if ( abs( self.tilt ) < tilt_rate )
		self.tilt = tilt_rate;
}

tilt_right( max_turn_speed, tilt_rate )
{
	if ( self.offset >= self.safe_offset )
	{
		self.goal_dir = 0;
		return;
	}

	self.tilt += tilt_rate;
	if ( self.tilt >= max_turn_speed )
		self.tilt = max_turn_speed;
}

tilt_left( max_turn_speed, tilt_rate )
{
	if ( self.offset < self.safe_offset * - 1 )
	{
		self.goal_dir = 0;
		return;
	}

	self.tilt -= tilt_rate;
	if ( self.tilt < max_turn_speed * - 1 )
		self.tilt = max_turn_speed * - 1;
}

/*

bike_ent_drives_path( ent, targ )
{
	bike = ent.bike;
	ent endon( "stop_bike" );
	set_road_offset( targ );
	ent.offset = randomfloatrange( ent.right_offset * - 1, ent.right_offset );
	ent.progress_percent = 1;
	ent.offset_percent = 0.5;
	ent.tilt = 0;
	ent.targ = targ;
	next_targ = targ;

	thread bike_avoids_obstacles( bike );

	next_turn = gettime() + randomfloatrange( 1000, 4000 );
	count = 0;
	sides = [];
	sides[ sides.size ] = "left";
	sides[ sides.size ] = "right";

	frame_time = 0.05;

	for ( ;; )
	{
		if ( ent.progress_percent >= 1.0 )
		{
			ent.progress_percent -= 1.0;
			targ = next_targ;
			if ( !isdefined( targ.target ) )
				break;

			next_targ = getent( targ.target, "targetname" );
			ent.targ = targ;

			count++ ;
			ent.next_targ = next_targ;

			set_road_offset( targ );
		}

		ent.offset += ent.tilt * 0.05;
		if ( ent.offset > ent.right_offset )
			ent.offset = ent.right_offset;
		else
		if ( ent.offset < ent.right_offset * - 1 )
			ent.offset = ent.right_offset * - 1;

		road_width = targ.road_width;

		column_position = [];
		foreach ( side in sides )
		{
			column_position[ side ] = targ.origins[ side ] * ( 1 - ent.progress_percent ) + next_targ.origins[ side ] * ent.progress_percent;
		}
		current_column_position = column_position[ "left" ] * ent.offset_percent + column_position[ "right" ] * ( 1 - ent.offset_percent );

		row_position = [];
		row_position[ "front" ] = targ.origins[ "left" ] * ent.offset_percent + targ.origins[ "right" ] * ( 1 - ent.offset_percent );
		row_position[ "back" ] = next_targ.origins[ "left" ] * ent.offset_percent + next_targ.origins[ "right" ] * ( 1 - ent.offset_percent );

		ent.origin = current_column_position;
		ent.angles = vectortoangles( row_position[ "front" ] - row_position[ "back" ] );

		dist = distance( row_position[ "front" ], row_position[ "back" ] );
		ent.progress = dist * ent.progress_percent;

		current_progress = ent.progress;
		next_progress = ent.progress + ent.speed * frame_time;

		ent.progress_percent = next_progress / dist;

		wait( frame_time );
	}

	flag_set( "biker_reaches_path_end", ent );
	ent notify( "end_path" );
}
*/

get_player_progress()
{
	if ( isdefined( level._player.progress ) )
	{
		return level._player.progress;
	}
	
	return 0;
}

get_player_targ()
{
	if ( isdefined( level._player.targ ) )
		return level._player.targ;
	return level._snowmobile_path[0];
}

debug_bike_line()
{
	color = ( 0.2, 0.2, 1.0 );
	if ( isdefined( level._player.vehicle ) && self.veh_speed > level._player.vehicle.veh_speed )
		color = ( 1.0, 0.2, 0.2 );
	
	Line( self.old_pos, self.origin, color, 1, 0, 50000 );
	self.old_pos = self.origin;
}