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

UPDATE_TIME = 0.05;		// Time between player input checks.
BLEND_TIME = 0.5;		// Blend time for lean animations.
STEER_MIN = -1.0;
STEER_MAX = 1.0;
TURN_ANIM_RATE_MULTIPLIER = 0.5;
CONST_MPHCONVERSION = 17.6;

SHOOT_BLEND_TIME = 0.1;
SHOOT_ARM_UP_DELAY = 1.0;
SHOOT_FIRE_TIME = 0.05;
SHOOT_AMMO_COUNT = 32;

SLEEVE_FLAP_SPEED = 65.0;
SLEEVE_FLAP_MAX_RATE = 1.5;
SLEEVE_FLAP_MIN_RATE = 0.75;
SLEEVE_FLAP_MAX_WEIGHT = 1.0;
SLEEVE_FLAP_MIN_WEIGHT = 0.1;

TACH_BLEND_UP = 0.2;
TACH_BLEND_DOWN = 1.3;
TACH_RAND_UP = 0.3;
TACH_RAND_DOWN = 0.8;
SPEEDOMETER_MAX_SPEED = 180.0;
SPEEDOMETER_BLEND_TIME = 0.1;
THROTTLE_BLEND_TIME = 0.08;
STEERING_BLEND_TIME = 0.08;


snowmobile_preLoad( playerHandModel, playerSnowmobileModel )
{
	flag_init( "player_can_die_on_snowmobile" );
	flag_set( "player_can_die_on_snowmobile" );

	// set player hand model
	if ( !isdefined( playerHandModel ) )
		level._snowmobile_playerHandModel = "viewhands_player_arctic_wind";
	else
		level._snowmobile_playerHandModel = playerHandModel;

	// set player snowmobile model
	if ( !isdefined( playerSnowmobileModel ) )
		level._snowmobile_playerSnowmobileModel = "vehicle_snowmobile_player";
	else
		level._snowmobile_playerSnowmobileModel = playerSnowmobileModel;

	// set gun
	if ( !isdefined( level._snowmobile_gunModel ) )
		level._snowmobile_gunModel = "viewmodel_glock";
	level._snowmobile_gun = "snowmobile_glock";

	// precahe models and itmes
	PreCacheModel( level._snowmobile_playerHandModel );
	PreCacheModel( level._snowmobile_playerSnowmobileModel );
	PreCacheModel( level._snowmobile_gunModel );
	PreCacheItem( level._snowmobile_gun );
	PreCacheRumble( "pistol_fire_auto" );

	// load gun effects
	level._snowmobile_gunFlashFx = LoadFX( "muzzleflashes/uzi_flash_view" );
	level._snowmobile_gunShellFx = LoadFX( "shellejects/pistol_view" );

	snowmobile_anims();

	// Hold ^3[{+speed_throw}]^7 to shoot.
	add_hint_string( "snowmobile_attack_player1", &"SCRIPT_PLATFORM_SNOWMOBILE_ATTACK", ::should_stop_snowmobile_attack_hint_player1 );
	add_hint_string( "snowmobile_attack_player2", &"SCRIPT_PLATFORM_SNOWMOBILE_ATTACK", ::should_stop_snowmobile_attack_hint_player2 );
	// Hold ^3[{+attack}]^7 to drive.
	add_hint_string( "snowmobile_drive_player1", &"SCRIPT_PLATFORM_SNOWMOBILE_DRIVE", ::should_stop_snowmobile_drive_hint_player1 );
	add_hint_string( "snowmobile_drive_player2", &"SCRIPT_PLATFORM_SNOWMOBILE_DRIVE", ::should_stop_snowmobile_drive_hint_player2 );
	// Press ^3[{+stance}]^7 to go in reverse.
	add_hint_string( "snowmobile_reverse_player1", &"SCRIPT_PLATFORM_SNOWMOBILE_REVERSE", ::should_stop_snowmobile_reverse_hint_player1 );
	add_hint_string( "snowmobile_reverse_player2", &"SCRIPT_PLATFORM_SNOWMOBILE_REVERSE", ::should_stop_snowmobile_reverse_hint_player2 );
}

drive_vehicle()
{
	Assert( self.code_classname == "script_vehicle" );
	vehicle = self;
	vehicle MakeUsable();

	self waittill( "vehicle_mount", player );
	Assert( IsDefined( player ) );
	Assert( player.classname == "player" );
	
	if ( !player ent_flag_exist( "player_shot_on_snowmobile" ) )
		player ent_flag_init( "player_shot_on_snowmobile" );
	
	attack_hint_string = "snowmobile_attack_player1";
	drive_hint_string = "snowmobile_drive_player1";
	if ( player != level._player )
	{
		assert( is_in_array( level._players, player ) );
		attack_hint_string = "snowmobile_attack_player2";
		drive_hint_string = "snowmobile_drive_player2";
	}
	player delayThread( 20, ::display_hint, attack_hint_string );
	player delayThread( 3, ::display_hint, drive_hint_string );

	delayThread( 3, ::add_snowmobile_autosave_checks );

	vehicle.player = player;
	player.vehicle = vehicle;
	
	vehicle thread add_rumble_for_notify( "veh_landed", "damage_heavy", player );
	vehicle thread add_rumble_for_notify( "veh_jolt", "damage_light", player );

	vehicle.snowmobile_3rdPersonModel = vehicle.model;
	vehicle.snowmobileAmmoCount = SHOOT_AMMO_COUNT;

	vehicle.animname = "snowmobile_player";
	vehicle assign_animtree();

	vehicle MakeUnusable();
	vehicle DontCastShadows();

	player thread reverse_hint( vehicle );
	player thread drive_target_enemy( vehicle );
	player thread drive_crash_detection( vehicle );
	player thread drive_camera( vehicle );

	player thread drive_notetrack_sounds( vehicle, "pullout_anim" );
	player thread drive_notetrack_sounds( vehicle, "fire_anim" );
	player thread drive_notetrack_sounds( vehicle, "reload_anim" );
	player thread drive_notetrack_sounds( vehicle, "putaway_anim" );

	if ( is_coop() )
		player thread make_coop_vehicle( vehicle );
	if ( is_specialop() )
		vehicle thread handle_riders( player );

	player drive_switch_to_1st_person( vehicle );
	vehicle waittill_either( "vehicle_dismount", "death" );
	player drive_switch_to_3rd_person( vehicle );

	remove_extra_autosave_check( "snowmobile_speed" );
	remove_extra_autosave_check( "snowmobile_fov" );
	player.vehicle = undefined;
}

add_rumble_for_notify( message, rumble, player )
{
	self endon( "vehicle_dismount" );
	self endon( "death" );
	
	for ( ;; )
	{
		self waittill( message );
		player playrumbleonentity( rumble );
	}
}

handle_riders( player )
{
	wait 0.05;
	if ( !isdefined( self ) )
		return;
	if ( !isalive( player ) )
		return;
	
	array_call( self.riders, ::hideOnClient, player );
	
	foreach( rider in self.riders )
	{
		rider hideOnClient( player );
		rider place_weapon_on( "usp", "left" );
		rider.primaryweapon = "usp";
	}
	
	if ( is_coop() )
	{
		array_thread( self.riders, ::set_ignoreall, true );
		array_thread( self.riders, ::magic_bullet_shield );
	}
	else
	{
		array_call( self.riders, ::Delete );
	}
}

add_snowmobile_autosave_checks()
{
	add_extra_autosave_check( "snowmobile_speed", ::snowmobile_autosave_check, "^3Player was riding too slow" );
	if ( IsDefined( level._snowmobile_path ) )
		add_extra_autosave_check( "snowmobile_fov", ::snowmobile_fov_check, "Player was pointed the wrong way" );
}

reverse_hint( vehicle )
{
	self endon( "death" );
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );

	vehicle wait_for_vehicle_to_move();

	vehicle.hint_brake_count = 0;
	for ( ;; )
	{
		if ( abs( vehicle.veh_speed ) < 5 )
		{
			vehicle.hint_brake_count++;
			if ( vehicle.hint_brake_count >= 3 )
			{
				reverse_hint_string = "snowmobile_reverse_player1";
				if ( vehicle.player != level._player )
				{
					assert( is_in_array( level._players, vehicle.player ) );
					reverse_hint_string = "snowmobile_reverse_player2";
				}
				vehicle.player.reverse_hint_string = true;
				vehicle.player display_hint( reverse_hint_string );
			}
		}
		else
		{
			vehicle.hint_brake_count = 0;
		}
		wait( 1 );
	}
}

should_stop_snowmobile_reverse_hint_player1()
{
	return level._player should_stop_snowmobile_reverse_hint();
}

should_stop_snowmobile_reverse_hint_player2()
{
	return level._player2 should_stop_snowmobile_reverse_hint();
}

should_stop_snowmobile_reverse_hint()
{
	if ( !isdefined( self.vehicle ) )
		retVal = true;
	else
	if ( !isdefined( self.vehicle.hint_brake_count ) )
		retVal = true;
	else
	if ( self ent_flag_exist( "finish_line" ) && self ent_flag( "finish_line" ) )
		retVal = true;
	else
		retVal = self.vehicle.hint_brake_count < 3;
	
	if ( retVal )
		self.reverse_hint_string = undefined;
	
	return retVal;
}

wait_for_vehicle_to_move()
{
	for ( ;; )
	{
		if ( self.veh_speed > 10 )
			return;
		wait( 1 );
	}
}

make_coop_vehicle( vehicle )
{
	clientOther = level._player2;
	if ( self != level._player )
		clientOther = level._player;
	
	// hide arms and player model from other players
	self hideOnClient( clientOther );
	//vehicle hideOnClient( clientOther );
	
	// use full snowmobile model with high res cockpit for coop
	//wait 0.05;
	//vehicle setModel( "vehicle_snowmobile_co_op" );
}

snowmobile_fov_check()
{
	AssertEx( IsDefined( level._player.targ ), "Tried to do autosave snowmobile fov check with no player targ." );
	player_angles = level._player.vehicle.angles;
	player_angles = ( 0, player_angles[ 1 ], 0 );
	player_forward = AnglesToForward( player_angles );

	node = level._player.targ;
	node_angles = VectorToAngles( node.next_node.origin - node.origin );
	node_angles = ( 0, node_angles[ 1 ], 0 );
	node_forward = AnglesToForward( node_angles );

	dot = VectorDot( player_forward, node_forward );
	return dot > 0.7;
}

snowmobile_autosave_check()
{
	AssertEx( IsDefined( level._player.vehicle ), "Player has no vehicle but tried to do snowmobile autosave" );
	return level._player.vehicle Vehicle_GetSpeed() > 60;
}

drive_target_enemy( vehicle )
{
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );

	for ( ;; )
	{
		ai = GetAIArray( "bad_guys" );
		highest_fov = 0.94;
		enemy = undefined;
		my_org = set_z( self.origin, 0 );

		foreach ( guy in ai )
		{
			his_org = set_z( guy.origin, 0 );
			if ( Distance( his_org, my_org ) > 750 )
				continue;

			fov = get_dot( my_org, self.angles, his_org );
			if ( fov > highest_fov )
			{
				highest_fov = fov;
				enemy = guy;
			}
		}

		self.snowmobile_enemy = enemy;
		wait( 0.2 );
	}
}


drive_crash_detection( vehicle )
{
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );
	level endon( "avalanche_begins" );

	vehicle waittill_vehicle_crashes();

	yaw_velocity = vehicle Vehicle_GetSpeed();
	yaw_velocity *= CONST_MPHCONVERSION;
	velocity = ( 0, yaw_velocity, 64 );

	if ( IsDefined( level._vehicle_crash_func ) )
		self thread [[ level._vehicle_crash_func ]]( vehicle );

	self thread drive_crash_slide( vehicle, velocity );
	self player_dismount_vehicle();
}

waittill_vehicle_crashes()
{
	if ( !is_specialop() )
		level endon( "player_crashes" );// from triggers in the map
	else
		thread waittill_vehicle_falling_so();
		
	self waittill_any( "veh_collision", "veh_falling" );
}

// This could be done cleaner for both SO and SP, but avoiding making changes to Cliffhanger.
waittill_vehicle_falling_so()
{
	trigger_ent = GetEnt( "player_crashes_trigger", "script_noteworthy" );
	while( 1 )
	{
		trigger_ent waittill( "trigger", player );
		if ( !isdefined( player ) || !isplayer( player ) )
			continue;
		if ( player.snowmobile == self )
		{
			self notify( "veh_falling" );
			return;
		}
	}
}


drive_crash_slide( vehicle, velocity )
{
	vehicle waittill( "vehicle_dismount" );

	self BeginSliding( velocity );

	if ( flag( "player_can_die_on_snowmobile" ) )
	{
		self kill_wrapper();
	}

	wait( 1.0 );

	//self EndSliding();
}


drive_camera( vehicle )
{
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );

	for ( ;; )
	{
		vehicle waittill( "third_person" );
		self drive_switch_to_3rd_person( vehicle );

		vehicle waittill( "first_person" );
		self drive_switch_to_1st_person( vehicle );
	}
}


drive_notetrack_sounds( vehicle, animflag )
{
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );

	for ( ;; )
	{
		vehicle waittill( animflag, notetrack );

		prefix = GetSubStr( notetrack, 0, 3 );

		if ( prefix == "ps_" )
		{
			alias = GetSubStr( notetrack, 3 );
			vehicle PlaySound( alias );
			continue;
		}
	}
}


drive_switch_to_1st_person( vehicle )
{
	if ( IsDefined( vehicle.firstPerson ) )
		return;

	vehicle SetModel( level._snowmobile_playerSnowmobileModel );
	vehicle Attach( level._snowmobile_playerHandModel, "tag_player" );
	vehicle ClearAnim( vehicle getanim( "root" ), 0 );

	vehicle.firstPerson = true;

	self thread drive_firstperson_anims( vehicle );
}


drive_switch_to_3rd_person( vehicle )
{
	if ( !isDefined( vehicle.firstPerson ) )
		return;

	if ( IsDefined( vehicle.gun_attached ) )
	{
		vehicle Detach( level._snowmobile_gunModel, "tag_weapon_left" );
		vehicle.gun_attached = undefined;
	}

	vehicle Detach( level._snowmobile_playerHandModel, "tag_player" );
	vehicle SetModel( vehicle.snowmobile_3rdPersonModel );
	vehicle ClearAnim( vehicle getanim( "root" ), 0 );

	vehicle.firstPerson = undefined;

	vehicle notify( "kill_anims" );
}


drive_firstperson_anims( vehicle )
{
	vehicle endon( "vehicle_dismount" );
	vehicle endon( "death" );
	vehicle endon( "kill_anims" );

	self childthread drive_turning_anims( vehicle );
	self childthread drive_shooting_anims( vehicle );
	self childthread drive_sleeve_anims( vehicle );
	self childthread drive_speedometer_anims( vehicle );
	self childthread drive_tachometer_anims( vehicle );
	self childthread drive_throttle_anims( vehicle );
}


drive_turning_anims( vehicle )
{
	lastDirection = 1;
	movement = 0;

	oldAnim = "turn_right2left_";
	newAnim = "turn_left2right_";
	curAnim = newAnim;

	animLength[ newAnim ][ "L" ] = GetAnimLength( vehicle getanim( newAnim + "L" ) );
	animLength[ newAnim ][ "R" ] = GetAnimLength( vehicle getanim( newAnim + "R" ) );

	animLength[ oldAnim ][ "L" ] = GetAnimLength( vehicle getanim( oldAnim + "L" ) );
	animLength[ oldAnim ][ "R" ] = GetAnimLength( vehicle getanim( oldAnim + "R" ) );

	vehicle SetAnim( vehicle getanim( "left_arm" ), 1.0, 0.0, 1.0 );
	vehicle SetAnim( vehicle getanim( "right_arm" ), 1.0, 0.0, 1.0 );

	vehicle SetAnimLimited( vehicle getanim( curAnim + "L" ), 1.0, BLEND_TIME, 0.0 );
	vehicle SetAnimLimited( vehicle getanim( curAnim + "R" ), 1.0, BLEND_TIME, 0.0 );

	vehicle SetAnimTime( vehicle getanim( curAnim + "L" ), 0.5 );
	vehicle SetAnimTime( vehicle getanim( curAnim + "R" ), 0.5 );

	for ( ;; )
	{
		//-------------------------------------------------------
		// Steer the handlebars based on the player's input
		//-------------------------------------------------------
		movement_last = movement;
		movement = vehicle Vehicle_GetSteering() * -1.0;
		movementChange = movement - movement_last;
		steerValue = movement;
		steerValue = clamp( steerValue, STEER_MIN, STEER_MAX );
		
		//-------------------------------------------------------
		// Blend turn anims on the vehicle with the right weights
		//-------------------------------------------------------

		newDirection = false;
		
		if ( movementChange < 0 )
		{
			// change to turn left anims
			if ( lastDirection != -1 )
				newDirection = true;
			lastDirection = -1;

			oldAnim = "turn_left2right_";
			newAnim = "turn_right2left_";
		}
		if ( movementChange > 0 )
		{
			// change to turn right anims
			if ( lastDirection != 1 )
				newDirection = true;
			lastDirection = 1;

			oldAnim = "turn_right2left_";
			newAnim = "turn_left2right_";
		}
		
		//---------------------------
		// Animate the bars and hands
		//---------------------------

		// See where the opposite animation needs to start so that it matches the previous animations position
		newAnimStartTime[ "L" ] = vehicle GetAnimTime( vehicle getanim( curAnim + "L" ) );
		newAnimStartTime[ "R" ] = vehicle GetAnimTime( vehicle getanim( curAnim + "R" ) );
		
		if ( newDirection )
		{
			newAnimStartTime[ "L" ] = abs( 1 - newAnimStartTime[ "L" ] );
			newAnimStartTime[ "R" ] = abs( 1 - newAnimStartTime[ "R" ] );
		}

		// See what the new animation needs to be to match the player's input
		animTimeGoal = abs( ( steerValue - STEER_MIN ) / ( STEER_MIN - STEER_MAX ) );
		if ( newAnim == "turn_right2left_" )
			animTimeGoal = 1.0 - animTimeGoal;
		animTimeGoal = cap_value( animTimeGoal, 0.0, 1.0 );
		
		if ( animTimeGoal < newAnimStartTime[ "L" ] )
		{
			newAnimStartTime[ "L" ] = animTimeGoal;
		}
		
		// Fix for bug where handlebars would sometimes get stuck left or right when there is no stick movement.
		// Happens when stick goes from one side to the other very quickly within 0.05 server framerate.
		// Just setting newDirection fixes this because it causes it to animate back to where it should be instead of overshooting it's goal time.
		if( newAnimStartTime[ "L" ] > animTimeGoal )
		{
			newDirection = true;
		}
		
		// See how far the new animation needs to travel
		amountChange[ "L" ] = abs( animTimeGoal - newAnimStartTime[ "L" ] );
		amountChange[ "R" ] = abs( animTimeGoal - newAnimStartTime[ "R" ] );

		// See what animrate we should play the animation at so that it reaches the animGoalTime over UPDATE_TIME time.
		if ( movementChange == 0 )
		{
			estimatedAnimRate[ "L" ] = 0;
			estimatedAnimRate[ "R" ] = 0;
		}
		else
		{
			estimatedAnimRate[ "L" ] = abs( ( animLength[ newAnim ][ "L" ] / UPDATE_TIME ) * amountChange[ "L" ] ) * TURN_ANIM_RATE_MULTIPLIER;
			estimatedAnimRate[ "R" ] = abs( ( animLength[ newAnim ][ "R" ] / UPDATE_TIME ) * amountChange[ "R" ] ) * TURN_ANIM_RATE_MULTIPLIER;
		}

		if ( newDirection )
		{
			// clear the anim from the other direction
			vehicle ClearAnim( vehicle getanim( oldAnim + "L" ), 0 );
			vehicle ClearAnim( vehicle getanim( oldAnim + "R" ), 0 );

			// set the time on the new direction anim so it doesn't start animating from the beginning,
			// since the previous anim probably wasn't at the end
			vehicle SetAnimLimited( vehicle getanim( newAnim + "L" ), 1, BLEND_TIME, estimatedAnimRate[ "L" ] );
			vehicle SetAnimLimited( vehicle getanim( newAnim + "R" ), 1, BLEND_TIME, estimatedAnimRate[ "R" ] );

			vehicle SetAnimTime( vehicle getanim( newAnim + "L" ), newAnimStartTime[ "L" ] );
			vehicle SetAnimTime( vehicle getanim( newAnim + "R" ), newAnimStartTime[ "R" ] );
		}
		else
		{
			vehicle SetAnimLimited( vehicle getanim( newAnim + "L" ), 1, BLEND_TIME, estimatedAnimRate[ "L" ] );
			vehicle SetAnimLimited( vehicle getanim( newAnim + "R" ), 1, BLEND_TIME, estimatedAnimRate[ "R" ] );
		}

		curAnim = newAnim;

		wait UPDATE_TIME;
	}
}


drive_magic_bullet( vehicle )
{
	start = vehicle GetTagOrigin( "tag_flash" );

	if ( IsAlive( self.snowmobile_enemy ) )
	{
		end = self.snowmobile_enemy GetEye();
	}
	else
	{
		angles = vehicle GetTagAngles( "tag_flash" );
		forward = AnglesToForward( angles );
		end = start + forward * 1500;
	}

	self PlayRumbleOnEntity( "pistol_fire_auto" );
	MagicBullet( level._snowmobile_gun, start, end, self );

	PlayFXOnTag( level._snowmobile_gunFlashFx, vehicle, "tag_flash" );
	PlayFXOnTag( level._snowmobile_gunShellFx, vehicle, "tag_brass" );
}


drive_blend_anims_with_steering( vehicle, animflag, endNotify, leftAnim, centerAnim, rightAnim )
{
	vehicle endon( endNotify );

	vehicle SetFlaggedAnimRestart( animflag, vehicle getanim( leftAnim ), 0.001, STEERING_BLEND_TIME, 1.0 );
	vehicle SetFlaggedAnimRestart( animflag, vehicle getanim( centerAnim ), 0.001, STEERING_BLEND_TIME, 1.0 );
	vehicle SetFlaggedAnimRestart( animflag, vehicle getanim( rightAnim ), 0.001, STEERING_BLEND_TIME, 1.0 );

	for ( ;; )
	{
		steerValue = vehicle Vehicle_GetSteering() * -1.0;
		steerValue = clamp( steerValue, STEER_MIN, STEER_MAX );

		// never set a weight to zero so that all the anims continue to play
		if ( steerValue >= 0.0 )
		{
			leftWeight = 0.001;
			centerWeight = -0.999 * steerValue + 1.0;
			rightWeight = 0.999 * steerValue + 0.001;
		}
		else
		{
			leftWeight = -0.999 * steerValue + 0.001;
			centerWeight = 0.999 * steerValue + 1.0;
			rightWeight = 0.001;
		}

		vehicle SetFlaggedAnim( animflag, vehicle getanim( leftAnim ), leftWeight, STEERING_BLEND_TIME, 1.0 );
		vehicle SetFlaggedAnim( animflag, vehicle getanim( centerAnim ), centerWeight, STEERING_BLEND_TIME, 1.0 );
		vehicle SetFlaggedAnim( animflag, vehicle getanim( rightAnim ), rightWeight, STEERING_BLEND_TIME, 1.0 );

		wait UPDATE_TIME;
	}
}

is_shoot_button_pressed()
{
	if ( !level._console )
	{
		// pc
		return self AttackButtonPressed();
	}

	// console
	return self VehicleAttackButtonPressed();
}


drive_shooting_update_anims( vehicle )
{
	// start pull out anim
	vehicle SetAnimKnobLimited( vehicle getanim( "gun_pullout_root" ), 1.0, 0.0, 1.0 );
	self childthread drive_blend_anims_with_steering( vehicle, "pullout_anim", "pullout_done", "gun_pullout_L", "gun_pullout", "gun_pullout_R" );

	// attach the gun
	vehicle waittillmatch( "pullout_anim", "attach_gun" );
	vehicle Attach( level._snowmobile_gunModel, "tag_weapon_left" );
	hideParts = [];
	hideParts[ "TAG_EOTECH" ] = true;
	hideParts[ "TAG_RAIL" ] = true;
	hideParts[ "TAG_RED_DOT" ] = true;
	hideParts[ "TAG_SILENCER" ] = true;

	foreach ( part, _ in hideParts )
	{
		vehicle HidePart( part );
	}



	vehicle.gun_attached = true;

	vehicle waittillmatch( "pullout_anim", "end" );
	vehicle notify( "pullout_done" );

	// start gun anim
	vehicle SetAnim( vehicle getanim( "glock" ), 1.0, 0.0, 1.0 );

	// start idle
	vehicle SetAnimKnobLimited( vehicle getanim( "gun_idle" ), 1.0, 0.0, 1.0 );

	vehicle.snowmobileShootTimer = SHOOT_ARM_UP_DELAY;

	for ( ;; )
	{
		if ( vehicle.snowmobileShootTimer <= 0.0 )
			break;

		shootButtonPressed = is_shoot_button_pressed();

		if ( shootButtonPressed && ( vehicle.snowmobileAmmoCount > 0 ) )
		{
			assert( isplayer( self ) );
			self ent_flag_set( "player_shot_on_snowmobile" );
			// play gun fire anims
			vehicle SetFlaggedAnimKnobLimitedRestart( "fire_anim", vehicle getanim( "gun_fire" ), 1.0, 0.0, 1.0 );

			if ( vehicle.snowmobileAmmoCount == 1 )
				vehicle SetAnimKnobLimitedRestart( vehicle getanim( "glock_last_fire" ), 1.0, 0.0, 1.0 );
			else
				vehicle SetAnimKnobLimitedRestart( vehicle getanim( "glock_fire" ), 1.0, 0.0, 1.0 );

			// fire bullet
			self drive_magic_bullet( vehicle );

			wait( SHOOT_FIRE_TIME );

			vehicle.snowmobileAmmoCount -= 1;
			vehicle.snowmobileShootTimer = SHOOT_ARM_UP_DELAY;
		}
		else if ( vehicle.snowmobileAmmoCount <= 0 )
		{
			// play reload anims
			vehicle SetFlaggedAnimKnobLimitedRestart( "reload_anim", vehicle getanim( "gun_reload" ), 1.0, 0.0, 1.0 );
			vehicle SetAnimKnobLimitedRestart( vehicle getanim( "glock_reload" ), 1.0, 0.0, 1.0 );

			vehicle waittillmatch( "reload_anim", "end" );

			vehicle.snowmobileAmmoCount = SHOOT_AMMO_COUNT;
			vehicle.snowmobileShootTimer = SHOOT_ARM_UP_DELAY;
		}
		else
		{
			// play idle
			vehicle SetAnimKnobLimited( vehicle getanim( "gun_idle" ), 1.0, 0.0, 1.0 );
			vehicle.snowmobileShootTimer -= UPDATE_TIME;
		}

		wait UPDATE_TIME;
	}

	// start put away anim
	vehicle SetAnimKnobLimited( vehicle getanim( "gun_putaway_root" ), 1.0, 0.0, 1.0 );
	self childthread drive_blend_anims_with_steering( vehicle, "putaway_anim", "putaway_done", "gun_putaway_L", "gun_putaway", "gun_putaway_R" );

	// detach the gun
	vehicle waittillmatch( "putaway_anim", "detach_gun" );
	vehicle Detach( level._snowmobile_gunModel, "tag_weapon_left" );
	vehicle.gun_attached = undefined;

	vehicle waittillmatch( "putaway_anim", "end" );
	vehicle notify( "putaway_done" );
	vehicle notify( "drive_shooting_done" );
}


drive_shooting_anims( vehicle )
{
	vehicle SetAnim( vehicle getanim( "drive_left_arm" ), 1.0, SHOOT_BLEND_TIME, 1.0 );
	vehicle SetAnim( vehicle getanim( "shoot_left_arm" ), 0.0, SHOOT_BLEND_TIME, 1.0 );

	for ( ;; )
	{
		shootButtonPressed = is_shoot_button_pressed();
		
		if ( shootButtonPressed )
		{
			vehicle SetAnim( vehicle getanim( "drive_left_arm" ), 0.001, SHOOT_BLEND_TIME, 1.0 );
			vehicle SetAnim( vehicle getanim( "shoot_left_arm" ), 1.0, SHOOT_BLEND_TIME, 1.0 );

			self childthread drive_shooting_update_anims( vehicle );

			vehicle waittill( "drive_shooting_done" );
		}

		vehicle SetAnim( vehicle getanim( "drive_left_arm" ), 1.0, SHOOT_BLEND_TIME, 1.0 );
		vehicle SetAnim( vehicle getanim( "shoot_left_arm" ), 0.0, SHOOT_BLEND_TIME, 1.0 );

		wait UPDATE_TIME;
	}
}


drive_sleeve_anims( vehicle )
{
	for ( ;; )
	{
		speed = vehicle Vehicle_GetSpeed();

		// Animate sleeve flapping based on speed
		speedLerp = speed / SLEEVE_FLAP_SPEED;
		if ( speedLerp > 1.0 )
			speedLerp = 1.0;

		rate = ( SLEEVE_FLAP_MAX_RATE - SLEEVE_FLAP_MIN_RATE ) * speedLerp + SLEEVE_FLAP_MIN_RATE;
		weight = ( SLEEVE_FLAP_MAX_WEIGHT - SLEEVE_FLAP_MIN_WEIGHT ) * speedLerp + SLEEVE_FLAP_MIN_WEIGHT;

		vehicle SetAnim( vehicle getanim( "sleeve_pose" ), ( 1.0 - weight ), BLEND_TIME, rate );
		vehicle SetAnim( vehicle getanim( "sleeve_flapping" ), weight, BLEND_TIME, rate );

		wait UPDATE_TIME;
	}
}


drive_speedometer_anims( vehicle )
{
	for ( ;; )
	{
		speed = vehicle Vehicle_GetSpeed();
		speedLerp = speed / SPEEDOMETER_MAX_SPEED;

		if ( speedLerp > 1.0 )
			speedLerp = 1.0;

		if ( speedLerp < 0.5 )
		{
			weight = speedLerp / 0.5;
			vehicle SetAnim( vehicle getanim( "speedometer_0" ), ( 1.0 - weight ), SPEEDOMETER_BLEND_TIME, 1.0 );
			vehicle SetAnim( vehicle getanim( "speedometer_90" ), weight, SPEEDOMETER_BLEND_TIME, 1.0 );
			vehicle SetAnim( vehicle getanim( "speedometer_180" ), 0.0, SPEEDOMETER_BLEND_TIME, 1.0 );
		}
		else
		{
			weight = ( speedLerp - 0.5 ) / 0.5;
			vehicle SetAnim( vehicle getanim( "speedometer_0" ), 0.0, SPEEDOMETER_BLEND_TIME, 1.0 );
			vehicle SetAnim( vehicle getanim( "speedometer_90" ), ( 1.0 - weight ), SPEEDOMETER_BLEND_TIME, 1.0 );
			vehicle SetAnim( vehicle getanim( "speedometer_180" ), weight, SPEEDOMETER_BLEND_TIME, 1.0 );
		}

		wait UPDATE_TIME;
	}
}


drive_tachometer_anims( vehicle )
{
	throttle = 0;

	for ( ;; )
	{
		throttle_last = throttle;
		throttle = vehicle Vehicle_GetThrottle();

		if ( ( throttle > throttle_last ) || ( throttle == 1.0 ) )
		{
			blend = TACH_BLEND_UP;
			weight = ( throttle - TACH_RAND_UP ) + RandomFloat( TACH_RAND_UP * 2.0 );
		}
		else
		{
			blend = TACH_BLEND_DOWN;
			weight = ( throttle - TACH_RAND_DOWN ) + RandomFloat( TACH_RAND_DOWN * 2.0 );
		}

		if ( weight < 0.0 )
			weight = 0.0;
		else if ( weight > 1.0 )
			weight = 1.0;

		vehicle SetAnim( vehicle getanim( "rpm_min" ), ( 1.0 - weight ), blend, 1.0 );
		vehicle SetAnim( vehicle getanim( "rpm_max" ), weight, blend, 1.0 );

		wait UPDATE_TIME;
	}
}


drive_throttle_anims( vehicle )
{
	vehicle SetAnim( vehicle getanim( "throttle" ), 1.0, 0.0, 1.0 );
	vehicle SetAnim( vehicle getanim( "throttle_left" ), 1.0, 0.0, 1.0 );
	vehicle SetAnim( vehicle getanim( "throttle_right" ), 1.0, 0.0, 1.0 );

	for ( ;; )
	{
		throttle = vehicle Vehicle_GetThrottle();
		steerValue = vehicle Vehicle_GetSteering() * -1.0;
		steerValue = clamp( steerValue, STEER_MIN, STEER_MAX );
		
		if ( steerValue >= 0.0 )
		{
			throttleWeight = throttle * ( 1.0 - steerValue );
			throttleLeftWeight = 0.0;
			throttleRightWeight = throttle * steerValue;
		}
		else
		{
			throttleWeight = throttle * ( 1.0 + steerValue );
			throttleLeftWeight = throttle * steerValue * -1.0;
			throttleRightWeight = 0.0;
		}

		vehicle SetAnim( vehicle getanim( "throttle_add" ), throttleWeight, THROTTLE_BLEND_TIME, 1.0 );
		vehicle SetAnim( vehicle getanim( "throttle_add_left" ), throttleLeftWeight, THROTTLE_BLEND_TIME, 1.0 );
		vehicle SetAnim( vehicle getanim( "throttle_add_right" ), throttleRightWeight, THROTTLE_BLEND_TIME, 1.0 );

		wait UPDATE_TIME;
	}
}


#using_animtree( "vehicles" );
snowmobile_anims()
{
	level._scr_animtree[ "snowmobile_player" ]	 = #animtree;
	level._scr_model[ "snowmobile_player" ]		 = level._snowmobile_playerHandModel;

	level._scr_anim[ "snowmobile_player" ][ "root" ]	 = %root;

	level._scr_anim[ "snowmobile_player" ][ "left_arm" ]				 = %player_snowmobile_left_arm;
	level._scr_anim[ "snowmobile_player" ][ "drive_left_arm" ]		 = %player_snowmobile_drive_left_arm;
	level._scr_anim[ "snowmobile_player" ][ "turn_left2right_L" ]	 = %player_snowmobile_drive_turn_left2right_L;
	level._scr_anim[ "snowmobile_player" ][ "turn_right2left_L" ]	 = %player_snowmobile_drive_turn_right2left_L;
	level._scr_anim[ "snowmobile_player" ][ "shoot_left_arm" ]		 = %player_snowmobile_shoot_left_arm;
	level._scr_anim[ "snowmobile_player" ][ "gun_fire" ]				 = %player_snowmobile_gun_fire;
	level._scr_anim[ "snowmobile_player" ][ "gun_idle" ]				 = %player_snowmobile_gun_idle;
	level._scr_anim[ "snowmobile_player" ][ "gun_pullout_root" ]		 = %player_snowmobile_gun_pullout_root;
	level._scr_anim[ "snowmobile_player" ][ "gun_pullout_L" ]		 = %player_snowmobile_gun_pullout_L;
	level._scr_anim[ "snowmobile_player" ][ "gun_pullout" ]			 = %player_snowmobile_gun_pullout;
	level._scr_anim[ "snowmobile_player" ][ "gun_pullout_R" ]		 = %player_snowmobile_gun_pullout_R;
	level._scr_anim[ "snowmobile_player" ][ "gun_putaway_root" ]		 = %player_snowmobile_gun_putaway_root;
	level._scr_anim[ "snowmobile_player" ][ "gun_putaway_L" ]		 = %player_snowmobile_gun_putaway_L;
	level._scr_anim[ "snowmobile_player" ][ "gun_putaway" ]			 = %player_snowmobile_gun_putaway;
	level._scr_anim[ "snowmobile_player" ][ "gun_putaway_R" ]		 = %player_snowmobile_gun_putaway_R;
	level._scr_anim[ "snowmobile_player" ][ "gun_reload" ]			 = %player_snowmobile_gun_reload;

	level._scr_anim[ "snowmobile_player" ][ "right_arm" ]			 = %player_snowmobile_right_arm;
	level._scr_anim[ "snowmobile_player" ][ "turn_left2right_R" ]	 = %player_snowmobile_drive_turn_left2right_R;
	level._scr_anim[ "snowmobile_player" ][ "turn_right2left_R" ]	 = %player_snowmobile_drive_turn_right2left_R;

	level._scr_anim[ "snowmobile_player" ][ "glock" ]			 = %snowmobile_glock;
	level._scr_anim[ "snowmobile_player" ][ "glock_fire" ]		 = %snowmobile_glock_fire;
	level._scr_anim[ "snowmobile_player" ][ "glock_last_fire" ]	 = %snowmobile_glock_last_fire;
	level._scr_anim[ "snowmobile_player" ][ "glock_reload" ]		 = %snowmobile_glock_reload;

	level._scr_anim[ "snowmobile_player" ][ "rpm_min" ]	 = %player_snowmobile_drive_rpm_min;
	level._scr_anim[ "snowmobile_player" ][ "rpm_max" ]	 = %player_snowmobile_drive_rpm_max;

	level._scr_anim[ "snowmobile_player" ][ "speedometer_0" ]	 = %player_snowmobile_drive_speedometer_0;
	level._scr_anim[ "snowmobile_player" ][ "speedometer_90" ]	 = %player_snowmobile_drive_speedometer_90;
	level._scr_anim[ "snowmobile_player" ][ "speedometer_180" ]	 = %player_snowmobile_drive_speedometer_180;

	level._scr_anim[ "snowmobile_player" ][ "throttle_add" ]	 = %player_snowmobile_drive_throttle_add;
	level._scr_anim[ "snowmobile_player" ][ "throttle" ]		 = %player_snowmobile_drive_throttle;

	level._scr_anim[ "snowmobile_player" ][ "throttle_add_left" ]	 = %player_snowmobile_drive_throttle_add_left;
	level._scr_anim[ "snowmobile_player" ][ "throttle_left" ]		 = %player_snowmobile_drive_throttle_left;

	level._scr_anim[ "snowmobile_player" ][ "throttle_add_right" ]	 = %player_snowmobile_drive_throttle_add_right;
	level._scr_anim[ "snowmobile_player" ][ "throttle_right" ]		 = %player_snowmobile_drive_throttle_right;

	level._scr_anim[ "snowmobile_player" ][ "sleeve_pose" ]		 = %player_sleeve_pose;
	level._scr_anim[ "snowmobile_player" ][ "sleeve_flapping" ]	 = %player_sleeve_flapping;
}

should_stop_snowmobile_attack_hint_player1()
{
	return level._player should_stop_snowmobile_attack_hint();
}

should_stop_snowmobile_attack_hint_player2()
{
	return level._player2 should_stop_snowmobile_attack_hint();
}

should_stop_snowmobile_attack_hint()
{
	if ( isdefined( self.reverse_hint_string ) )
		return true;
	
	if ( isDefined( level._no_snowmobile_attack_hint ) )
		return true;
	
	if ( !isdefined( self.vehicle ) )
		return true;
	
	if ( self ent_flag_exist( "finish_line" ) && self ent_flag( "finish_line" ) )
		return true;
	
	return self ent_flag( "player_shot_on_snowmobile" );
}

should_stop_snowmobile_drive_hint_player1()
{
	return level._player should_stop_snowmobile_drive_hint();
}

should_stop_snowmobile_drive_hint_player2()
{
	return level._player2 should_stop_snowmobile_drive_hint();
}

should_stop_snowmobile_drive_hint()
{
	if ( !isdefined( self.vehicle ) )
		return true;
	
	return self.vehicle.veh_speed > 10;
}

