#include maps\_utility;
#include animscripts\utility;
#include common_scripts\utility;
#include maps\_hud_util;

main()
{
	flag_init( "coop_game" );
	flag_init( "coop_revive" );
	flag_init( "coop_alldowned" );
	flag_init( "coop_show_constant_icon" );
	flag_init( "coop_fail_when_all_dead" );

	setDvarIfUninitialized( "coop_revive", 1 );
	setDvarIfUninitialized( "coop_show_constant_icon", 1 );

	//later on - hopefully this flag will be set through some real time method
	//so that the 2nd player can jump in and out of a game at any moment...right
	//now this check only happens at the load of each level and doesn't get checked again
	if ( is_coop() )
		flag_set( "coop_game" );
	if ( getdvar( "coop_revive", 1 ) == "1" )
		flag_set( "coop_revive" );

	flag_set( "coop_show_constant_icon" );

	// "Partner down"
	precacheString( &"SCRIPT_COOP_BLEEDING_OUT_PARTNER" );
	// "Bleeding out"
	precacheString( &"SCRIPT_COOP_BLEEDING_OUT" );
	// "Reviving partner..."
	precacheString( &"SCRIPT_COOP_REVIVING_PARTNER" );
	// "Being revived..."
	precacheString( &"SCRIPT_COOP_REVIVING" );
	// "Hold ^3[{+usereload}]^7 to revive"
	precacheString( &"SCRIPT_COOP_REVIVE" );

	precacheShader( "hint_health" );
	precacheShader( "coop_player_location" );
	precacheShader( "coop_player_location_fire" );

	level._coop_icon_green = ( 0.635, 0.929, 0.604 );
	level._coop_icon_yellow = ( 1, 1, 0.2 );
	level._coop_icon_orange = ( 1, .65, 0.2 );
	level._coop_icon_red = ( 1, 0.2, 0.2 );
	level._coop_icon_white = ( 1, 1, 1 );
	level._coop_icon_downed = level._coop_icon_yellow;
	level._coop_icon_shoot = level._coop_icon_green;
	level._coop_icon_damage = level._coop_icon_orange;
	
	level._coop_icon_blinktime = 7;  // how long the non-downed player's hud icon should blink after the downed player presses the nag button
	level._coop_icon_blinkcrement = 0.375;  // how long each "blink" lasts
	
	level._coop_revive_nag_hud_refreshtime = 20;

	level._revive_hud_base_offset = 75;
 	level._revive_bar_base_offset = 15;
	if ( !issplitscreen() )
		level._revive_hud_base_offset = 120;
 
	foreach( player in level._players )
		player.reviving_buddy = false;
		
	// Used to keep one player alive for a few seconds when the other player goes down
	level._coop_last_player_downed_time = 0;
	thread downed_player_manager();

	thread player_coop_check_mission_ended();
}

player_coop_proc()
{
	//this checks to see if we're already running the process
	if ( !flag( "coop_game" ) )
		return;
	level endon( "coop_game" );
	
	if ( self ent_flag( "coop_proc_running" ) )
		return;
	
	if ( !isdefined( self.original_maxhealth ) )
		self.original_maxhealth = self.maxhealth;
	
	if ( !flag( "coop_revive" ) )
		return;
	level endon( "coop_revive" );

	self thread player_coop_proc_ended();

	switch( level._gameskill )
	{
		case 0:
		case 1:	
			self.coop.bleedout_time_default = 120;
			level._coop_bleedout_stage2_multiplier = 0.5;
			level._coop_bleedout_stage3_multiplier = 0.25;
			break;
		case 2:	
			self.coop.bleedout_time_default = 90;	
			level._coop_bleedout_stage2_multiplier = 0.66;
			level._coop_bleedout_stage3_multiplier = 0.33;
			break;
		case 3:	
			self.coop.bleedout_time_default = 60;	
			level._coop_bleedout_stage2_multiplier = 0.5;
			level._coop_bleedout_stage3_multiplier = 0.25;
			break;
	}	

	self ent_flag_set( "coop_proc_running" );
	self EnableDeathShield( true );
	
	assertex( self ent_flag_exist( "coop_downed" ), "Didnt have flag set" );
	self ent_flag_clear( "coop_downed" );
	self ent_flag_clear( "coop_pause_bleedout_timer" );

	self thread player_setup_icon();

	self endon( "death" );
	
	my_id = self.unique_id;
	
	while ( 1 )
	{
		self waittill( "deathshield", damage, attacker, direction, point, type, modelName, tagName, partName, dflags, weaponName );
		
		// we're already downed
		if ( self ent_flag( "coop_downed" ) )
			continue;

		death_array = [];
		death_array[ "damage" ] = damage;
		death_array[ "player" ] = self;

		// Only want to set this on the *second* player to go down.
		// We want to know the most recent reason for a player to be killed, rather than something that
		// happened to the first player potentially almost 2 minutes ago (or whatever the revive time is).
		buddy = get_other_player( self );
		if ( buddy ent_flag( "coop_downed" ) )
		{
			self.coop_death_reason = [];
			self.coop_death_reason[ "attacker" ] 	= attacker;
			self.coop_death_reason[ "cause" ] 		= type;
			self.coop_death_reason[ "weapon_name" ] = weaponName;
		}
				
		level._downed_players[ self.unique_id ] = death_array;

		self try_crush_player( attacker, type );

		// the downed_player_manager will down the player
		level notify( "player_downed" );
	}
}

try_crush_player( attacker, type )
{
	if ( !Isdefined( attacker ) )
	{
		return;
	}

	if ( !Isdefined( type ) )
	{
		return;
	}

	if ( type != "MOD_CRUSH" )
	{
		return;
	}

	// If a vehicle crushed the player, make sure the vehicle is moving before we kill, if not, then the player should live
	if ( IsDefined( attacker.vehicletype ) )
	{
		speed = attacker Vehicle_GetSpeed();
		if ( abs( speed ) == 0 )
		{
			return;
		}
	}

	if ( flag( "special_op_terminated" ) )
	{
		return;
	}

	attacker maps\_specialops::so_crush_player( self, type );
}

downed_player_manager()
{
	for ( ;; )
	{
		// the array will be refilled when a player is downed
		level._downed_players = [];
		
		level waittill( "player_downed" );

		assertex( isdefined( level._player_downed_death_buffer_time ), "level.player_downed_death_buffer_time didnt get defined!" );
		
		// wait until the end of the frame so the array will have all players that were downed in it
		waittillframeend;

		if ( gettime() < level._coop_last_player_downed_time + level._player_downed_death_buffer_time * 1000 )
		{
			// cant die until this time has passed
			continue;
		}

		level._coop_last_player_downed_time = gettime();

		// figure out which player to down
		highest_damage = 0;
		downed_player = undefined;
		// randomize it so either player can be downed if they tie on damage		
		level._downed_players = array_randomize( level._downed_players );
		foreach ( unique_id, array in level._downed_players )
		{
			//Print3d( array[ "player" ] geteye(), array[ "damage" ], (1,0,0), 1, 1, 500 );
			if ( array[ "damage" ] >= highest_damage )
			{
				downed_player = array[ "player" ];
			}
		}
		assertex( isdefined( downed_player ), "Downed_player was not defined!" );
		
		downed_player thread player_coop_downed();
		
		// the remaining player gets slightly buffed
		thread maps\_gameskill::resetSkill();
	}
}

create_fresh_friendly_icon( material )
{
	if ( isdefined( self.friendlyIcon ) )
		self.friendlyIcon Destroy();
		
	self.friendlyIcon = NewClientHudElem( self );
	self.friendlyIcon SetShader( material, 1, 1 );
	self.friendlyIcon SetWayPoint( true, true, false );
	self.friendlyIcon SetWaypointIconOffscreenOnly();
	self.friendlyIcon SetTargetEnt( get_other_player( self ) );
	self.friendlyIcon.material = material;
	self.friendlyIcon.hidewheninmenu = true;

	if ( flag( "coop_show_constant_icon" ) )
		self.friendlyIcon.alpha = 1.0;
	else
		self.friendlyIcon.alpha = 0.0;
}

rebuild_friendly_icon( color, material, non_rotating )
{
	if ( isdefined( self.noFriendlyHudIcon ) )
		return;

	assertex( isdefined( color ), "rebuild_friendly_icon requires a valid color to be passed in." );
	assertex( isdefined( material ), "rebuild_friendly_icon requires a valid material to be passed in.");
	
	// Rebuild from scratch if it doesn't exist or the material has changed.
	if ( !isdefined( self.friendlyIcon ) || ( self.friendlyIcon.material != material ) )
	{
		create_fresh_friendly_icon( material );
	}

	self.friendlyIcon.color = color;
	if ( !isdefined( non_rotating ) )
		self.friendlyIcon SetWaypointEdgeStyle_RotatingIcon();
}

CreateFriendlyHudIcon_Normal()
{
	self rebuild_friendly_icon( level._coop_icon_green, "coop_player_location" );
}

CreateFriendlyHudIcon_Downed()
{
	self rebuild_friendly_icon( level._coop_icon_downed, "hint_health", true );
}

FriendlyHudIcon_BlinkWhenFire()
{
	self endon( "death" );

	while ( 1 )
	{
		self waittill( "weapon_fired" );
		other_player = get_other_player( self );
		other_player thread FriendlyHudIcon_FlashIcon( level._coop_icon_shoot, "coop_player_location_fire" );
	}
}

FriendlyHudIcon_BlinkWhenDamaged()
{
	self endon( "death" );

	while ( 1 )
	{
		self waittill( "damage" );
		other_player = get_other_player( self );
		other_player thread FriendlyHudIcon_FlashIcon( level._coop_icon_damage, "coop_player_location" );
	}
}

FriendlyHudIcon_FlashIcon( color, material )
{
	if ( isdefined( self.noFriendlyHudIcon ) )
		return;

	self endon( "death" );
	self notify( "flash_color_thread" );
	self endon( "flash_color_thread" );

	other_player = get_other_player( self );
	if ( other_player ent_flag( "coop_downed" ) )
		return;

	self rebuild_friendly_icon( color, material );
	wait .5;

	self rebuild_friendly_icon( level._coop_icon_green, "coop_player_location" );
}

player_setup_icon()
{
	if ( !flag( "coop_game" ) )
		return;
	level endon( "coop_game" );

	self CreateFriendlyHudIcon_Normal();
	self thread FriendlyHudIcon_BlinkWhenFire();
	self thread FriendlyHudIcon_BlinkWhenDamaged();

	if ( isdefined( self.noFriendlyHudIcon ) )
		return;

	while ( 1 )
	{
		flag_wait( "coop_show_constant_icon" );
		self.friendlyIcon.alpha = 1;

		flag_waitopen( "coop_show_constant_icon" );
		self.friendlyIcon.alpha = 0;
	}
}

player_coop_create_use_target()
{
	level._revive_ent = spawn( "script_model", self.origin + ( 0, 0, 28 ) ); // offset can't be higher than prone height of 30
//	level.revive_ent setModel( "defaultvehicle" );
	level._revive_ent setModel( "tag_origin" );
	level._revive_ent linkTo( self, "tag_origin" );
	level._revive_ent makeUsable();
	level._revive_ent setHintString( &"SCRIPT_COOP_REVIVE" );	// LANG_ENGLISH		Hold ^3[{+usereload}]^7 to revive"
}

player_coop_destroy_use_target()
{
	foreach ( player in level._players )
		player.reviving_buddy = false;
		
	if ( isdefined( level._revive_ent ) )
		level._revive_ent Delete();
}

player_coop_downed()
{
	self endon( "death" );

	self player_coop_set_down_attributes();

	player_coop_check_alldowned();

	if ( flag( "coop_alldowned" ) )
		self player_coop_kill();

	self player_coop_create_use_target();
	
	//put up icon
	self thread player_coop_downed_dialogue();
	self thread player_coop_downed_hud();
	self thread player_coop_downed_icon();
	self thread player_coop_enlist_savior();

	self add_wait( ::flag_wait, "coop_alldowned" );
	self add_wait( ::ent_flag_waitopen, "coop_downed" );
	self add_wait( ::waittill_msg, "coop_bled_out" );
	do_wait_any();

	//take down icon
	waittillframeend;
	self notify( "end_func_player_coop_downed_icon" );

	if ( flag( "coop_alldowned" ) || self ent_flag( "coop_downed" ) )
		self player_coop_kill();

	self player_coop_set_original_attributes();
}

player_coop_downed_dialogue()
{
	self endon( "death" );
	self endon( "revived" );
	level endon( "special_op_terminated" );

	other_player = get_other_player( self );	
	time = other_player.coop.bleedout_time_default;
	//time_fifth = time * 0.2;
	initialWait = 1;
	
	wait initialWait;
	self notify( "so_downed" );
	self delaythread( 0.05, ::player_coop_downed_nag_button );
}

get_coop_downed_hud_color( current_time, total_time, doBlinks )
{
	// only one player should see the blinking
	if( !IsDefined( doBlinks ) )
	{
		doBlinks = true;
	}
	
	// maybe we have to deliver the blink state?
	if( doBlinks && self coop_downed_hud_should_blink() )
	{
		ASSERT( IsDefined( self.blinkState ) );
		
		if( self.blinkState == 1 )
		{
			return level._coop_icon_white;
		}
	}
	
	// if we're not blinking, return the base color
	if ( current_time < ( total_time * level._coop_bleedout_stage3_multiplier ) )
	{
		return level._coop_icon_red;
	}
	
	if ( current_time < ( total_time * level._coop_bleedout_stage2_multiplier ) )
	{
		return level._coop_icon_orange;
	}

	return level._coop_icon_downed;
}

coop_downed_hud_should_blink()
{
	otherplayer = get_other_player( self );
	
	if( otherplayer player_coop_is_reviving() )
	{
		return false;
	}
	
	// have we ever pressed the nag button?
	if( IsDefined( self.lastReviveNagButtonPressTime ) )
	{
		// did we press the button recently?
		if( ( GetTime() - self.lastReviveNagButtonPressTime ) < ( level._coop_icon_blinktime * 1000 ) )
		{
			return true;
		}
	}
	
	return false;
}

// this toggles the blink state so we don't have to track increment time in the get_coop_downed_hud_color() function
player_downed_hud_toggle_blinkstate()
{
	self notify( "player_downed_hud_blinkstate" );
	self endon( "player_downed_hud_blinkstate" );
	self endon( "death" );
	self endon( "revived" );
	
	self.blinkState = 1;
	
	while( 1 )
	{
		wait( level._coop_icon_blinkcrement );
		
		if( self.blinkState == 1 )
		{
			self.blinkState = 0;
		}
		else
		{
			self.blinkState = 1;
		}
	}
}

player_coop_downed_nag_button()
{
	self endon( "death" );
	self endon( "revived" );
	level endon( "special_op_terminated" );
	
	self NotifyOnPlayerCommand( "nag", "weapnext" );
	
	while( 1 )
	{
		if( !self can_show_nag_prompt() )
		{
			wait( 0.05 );
			continue;
		}
		
		if( self nag_should_draw_hud() )
		{
			self thread nag_prompt_show();
			self thread nag_prompt_cancel();
		}
		
		msg = self waittill_any_timeout( level._coop_revive_nag_hud_refreshtime, "nag", "nag_cancel" );
		
		if( msg == "nag" )
		{
			self.lastReviveNagButtonPressTime = GetTime();
			self thread player_downed_hud_toggle_blinkstate();
			self thread maps\_specialops_battlechatter::play_revive_nag();
		}
		
		wait( 0.05 );
	}
}

nag_should_draw_hud()
{
	waitTime = level._coop_revive_nag_hud_refreshtime * 1000;
	
	if( !IsDefined( self.lastReviveNagButtonPressTime ) )
	{
		return true;
	}
	else if( GetTime() - self.lastReviveNagButtonPressTime < waitTime )
	{
		return false;
	}
	
	return true;
}

nag_prompt_show()
{
	fadeTime = 0.05;
	loc = &"SPECIAL_OPS_REVIVE_NAG_HINT";
	
	hud = self get_nag_hud();
	hud.alpha = 0;
	hud SetText( loc );
	hud FadeOverTime( fadeTime );
	hud.alpha = 1;
	
	self waittill_disable_nag();
	
	hud FadeOverTime( fadeTime );
	hud.alpha = 0;
	hud delaycall( ( fadeTime + 0.05 ), ::Destroy );
}

waittill_disable_nag()
{
	level endon( "special_op_terminated" );
	self waittill_any( "nag", "nag_cancel", "death" );
}

get_nag_hud()
{
	hudelem = NewClientHudElem( self );
	
	hudelem.x = 0;
	hudelem.y = 50;
	hudelem.alignX = "center";
	hudelem.alignY = "middle";
	hudelem.horzAlign = "center";
	hudelem.vertAlign = "middle";
	hudelem.fontScale = 1;
	hudelem.color = ( 1.0, 1.0, 1.0 );
	hudelem.font = "hudsmall";
	hudelem.glowColor = ( 0.3, 0.6, 0.3 );
	hudelem.glowAlpha = 0;
	hudelem.foreground = 1;
	hudelem.hidewheninmenu = true;
	hudelem.hidewhendead = true;
	return hudelem;
}

nag_prompt_cancel()
{
	self endon( "nag" );
	
	while( self can_show_nag_prompt() )
	{
		wait( 0.05 );
	}
	
	self notify( "nag_cancel" );
}

can_show_nag_prompt()
{
	if ( isdefined( level._hide_nag_prompt ) && level._hide_nag_prompt )
	{
		return false;
	}
		
	otherplayer = get_other_player( self );
	if( otherplayer player_coop_is_reviving() )
	{
		return false;
	}
	
	if( !self maps\_specialops_battlechatter::can_say_current_nag_event_type() )
	{
		return false;
	}
	
	return true;
}

player_coop_downed_hud()
{
	self endon( "end_func_player_coop_downed_icon" );
	self endon( "death" );
	self endon( "revived" );

	level endon( "special_op_terminated" );

	foreach ( player in level._players )
	{
		player.revive_text = player createClientFontString( "hudsmall", 1.0 );
		player.revive_text setPoint( "CENTER", undefined, 0, level._revive_hud_base_offset );
		player.revive_text thread maps\_specialops::so_hud_pulse_create();

		player.revive_timer = player createClientTimer( "hudsmall", 1.2 );
		player.revive_timer setPoint( "CENTER", undefined, 0, level._revive_hud_base_offset + level._revive_bar_base_offset );
		player.revive_timer thread maps\_specialops::so_hud_pulse_create();
	}

	self thread player_coop_downed_hud_destroy();

	foreach ( player in level._players )
	{
		if ( player == self )
			player.revive_text settext( &"SCRIPT_COOP_BLEEDING_OUT" );
		else
			player.revive_text settext( &"SCRIPT_COOP_BLEEDING_OUT_PARTNER" );
		player.revive_timer setTimer( self.coop.bleedout_time_default - 1 );
	}

	self thread player_coop_countdown_timer( self.coop.bleedout_time_default );

	time = self.coop.bleedout_time_default;

	foreach ( player in level._players )
	{
		player.revive_text.color = self.revive_text.color;
		player.revive_timer.color = self.revive_text.color;
	}
	
	// give player a chance to get his timer set
	waittillframeend;

	while ( time )
	{
		foreach ( player in level._players )
		{
			previous_color = player.revive_text.color;
			new_color = get_coop_downed_hud_color( self.coop.bleedout_time, self.coop.bleedout_time_default, false );
			player.revive_text.color = new_color;
			player.revive_timer.color = new_color;

			if ( distance( new_color, previous_color ) > 0.001 )
			{
				if ( distance( new_color, level._coop_icon_red ) <= 0.001 )
				{
					player.revive_text.pulse_loop = true;
					player.revive_timer.pulse_loop = true;
				}
					
				player.revive_text thread maps\_specialops::so_hud_pulse_create();
				player.revive_timer thread maps\_specialops::so_hud_pulse_create();
			}
		}
		
		wait 1.0;
		time -= 1.0;
	}
}

player_coop_downed_hud_destroy()
{
	self thread player_coop_downed_hud_destroy_mission_end();

	self waittill_any( "end_func_player_coop_downed_icon", "death" );
	foreach ( player in level._players )
	{
		if ( isdefined( player.revive_text ) )	
			player.revive_text destroy();
		if ( isdefined( player.revive_timer ) )
			player.revive_timer destroy();
	}
}

player_coop_downed_hud_destroy_mission_end()
{
	level waittill( "special_op_terminated" );

	foreach ( player in level._players )
	{
		if ( isdefined( player.revive_text ) )	
			player.revive_text destroy();
		if ( isdefined( player.revive_timer ) )
			player.revive_timer destroy();
	}

	if ( isdefined( self.friendlyIcon ) )
		self.friendlyIcon Destroy();
	other_player = get_other_player( self );
	if ( isdefined( other_player.friendlyIcon ) )
		other_player.friendlyIcon Destroy();
}

player_coop_countdown_timer( time )
{
	self endon( "death" );
	self endon( "revived" );

	level endon( "special_op_terminated" );

	self.coop.bleedout_time = time;

	while ( self.coop.bleedout_time > 0 )
	{
		if ( self ent_flag( "coop_pause_bleedout_timer" ) )
		{
			foreach ( player in level._players )
				player.revive_timer.alpha = 0;
			self ent_flag_waitopen( "coop_pause_bleedout_timer" );

			//need this check because was setting a time that wasn't greater than 0 which would give an error
			if ( self.coop.bleedout_time >= 1 )
			{
				foreach ( player in level._players )
					player.revive_timer settimer( self.coop.bleedout_time - 1 );
			}
		}
		else
		{
			foreach ( player in level._players )
				player.revive_timer.alpha = 1;
		}

		wait .05;
		self.coop.bleedout_time -= .05;
	}

	self.coop.bleedout_time = 0;
	maps\_specialops::so_force_deadquote( "@DEADQUOTE_SO_BLED_OUT", "ui_bled_out" );
	thread maps\_specialops::so_dialog_mission_failed_bleedout();
	self notify( "coop_bled_out" );
}

player_coop_downed_icon()
{
	self endon( "end_func_player_coop_downed_icon" );
	self endon( "death" );
	self endon( "revived" );

	level endon( "special_op_terminated" );

//	self thread player_coop_downed_icon_timer();

	//give player a chance to get his timer set
	waittillframeend;

	other_player = get_other_player( self );	
	other_player CreateFriendlyHudIcon_Downed();
	
	while ( self.coop.bleedout_time > 0 )
	{
		self ent_flag_waitopen( "coop_pause_bleedout_timer" );
		
		other_player rebuild_friendly_icon( get_coop_downed_hud_color( self.coop.bleedout_time, self.coop.bleedout_time_default ), "hint_health", true );
		wait 0.05;
	}
}
/*
player_coop_downed_icon_timer()
{
	self endon( "end_func_player_coop_downed_icon" );
	self endon( "death" );
	self endon( "revived" );

	level endon( "special_op_terminated" );
	
	//give player a chance to get his timer set
	waittillframeend;

	other_player = get_other_player( self );	

	while ( self.coop.bleedout_time > 0 )
	{
		self ent_flag_waitopen( "coop_pause_bleedout_timer" );

		origin = self.origin + ( 0, 0, 80 );
		_size = .25 + ( distance( self.origin, other_player.origin ) * .0015 );

		if ( self.model != "" )
		{
			origin = self gettagorigin( "tag_origin" );
			offset = vector_multiply( anglestoright( self gettagangles( "tag_origin" ) ), 5 );
			origin += offset;
		}

		string = convert_to_time_string( self.coop.bleedout_time );
		color = self get_coop_downed_hud_color( self.coop.bleedout_time, self.coop.bleedout_time_default );
		print3d( origin + ( 0, 0, 35 + ( _size * 12 ) ), string, color, .75, _size );
		wait .05;
	}
}
*/
player_coop_enlist_savior()
{
	savior = get_other_player( self );
	savior thread player_coop_revive_buddy();
}

player_coop_freeze_players( frozen )
{
	assert( isdefined( self ) );
	assert( isdefined( frozen ) );
	
	downed_buddy = get_other_player( self );
	assert( isdefined( downed_buddy ) );
			
	if ( frozen )
	{
		self freezecontrols( true );
		self disableweapons();
		self disableweaponswitch();

		downed_buddy freezecontrols( true );
		downed_buddy disableweapons();
	}
	else
	{
		self freezecontrols( false );
		self enableweapons();
		self enableweaponswitch();

		downed_buddy freezecontrols( false );
		if ( !is_player_down_and_out( downed_buddy ) )
			downed_buddy enableweapons();
	}
}

player_coop_revive_buddy()
{
	self endon( "death" );
	self endon( "revived" );

	level endon( "special_op_terminated" );

	downed_buddy = get_other_player( self );
	buttonTime = 0;
	for ( ;; )
	{
		level._revive_ent waittill( "trigger", player );
//		wait 0.05;

		if ( player != self )
			continue;

		player.reviving_buddy = true;
		if ( player_coop_is_reviving() )
		{
			self player_coop_freeze_players( true );
			
			// reset the other player's death protection if he initiates revive, so you can't infinitely revive each other
			level._coop_last_player_downed_time = 0;
			
			// Gives ability to reload by tapping support.
			wait 0.1;
			if ( !player_coop_is_reviving() )
			{
				player_coop_revive_buddy_cleanup( downed_buddy );				
				continue;
			}

			level._bars = [];
			level._bars[ "p1" ] = createClientProgressBar( level._player, level._revive_hud_base_offset + level._revive_bar_base_offset );
			level._bars[ "p2" ] = createClientProgressBar( level._player2, level._revive_hud_base_offset + level._revive_bar_base_offset );
		
			foreach ( player in level._players )
			{
				if ( player == downed_buddy )
					player.revive_text settext( &"SCRIPT_COOP_REVIVING" );
				else
					player.revive_text settext( &"SCRIPT_COOP_REVIVING_PARTNER" );
			}

			speak_first = randomfloat( 1 ) > 0.33;
			if ( speak_first )
				self notify( "so_reviving" );
			
			buttonTime = 0;
			totalTime = 1.5;
			while ( player_coop_is_reviving() )
			{
				downed_buddy ent_flag_set( "coop_pause_bleedout_timer" );
				foreach ( bar in level._bars )
					bar updateBar( buttonTime / totalTime );

				wait( 0.05 );
				buttonTime += 0.05;
				if ( buttonTime > totalTime )
				{
					player_coop_revive_buddy_cleanup( downed_buddy );

					//if we get to here - we double tapped	
					downed_buddy player_coop_revive_self();
					if ( !speak_first )
						self notify( "so_revived" );
					return;
				}
			}

			player_coop_revive_buddy_cleanup( downed_buddy );
		}
	}
}

player_coop_is_reviving()
{
	if ( !self UseButtonPressed() )
	{
		return false;
	}

	// Leaving this here, but noting that this causes an SRE, so if we need this sort of logic we'll need to do it a different way.
	// Also, level.default_use_radius is an invalid number to use, need to use the current value of dvar player_useradius
/*	if ( DistanceSquared( self.origin, level.revive_ent.origin ) > level.default_use_radius * level.default_use_radius )
	{
		return false;
	}*/

	return self.reviving_buddy;
}

player_coop_check_mission_ended()
{
	level waittill( "special_op_terminated" );
	
	player_coop_destroy_use_target();
	
	revive_hud_cleanup_bars();
}

player_coop_revive_buddy_cleanup( downed_buddy )
{
	level notify( "revive_bars_killed" );

	revive_hud_cleanup_bars();

	foreach ( player in level._players )
	{
		if ( player == downed_buddy )
			player.revive_text settext( &"SCRIPT_COOP_BLEEDING_OUT" );
		else
			player.revive_text settext( &"SCRIPT_COOP_BLEEDING_OUT_PARTNER" );
	}
	
	if ( isdefined( downed_buddy ) && isalive( downed_buddy ) )
	{
		downed_buddy ent_flag_clear( "coop_pause_bleedout_timer" );
	}
	
	if ( isdefined( self ) && isalive( self ) )
	{
		self.reviving_buddy = false;
		self player_coop_freeze_players( false );
	}
}

revive_hud_cleanup_bars()
{
	if ( isdefined( level._bars ) )
	{
		foreach ( bar in level._bars )
		{
			if ( isdefined( bar ) )
			{
				bar notify( "destroying" );
				bar destroyElem();
			}
		}
		level._bars = undefined;
	}
}

player_coop_revive_self()
{
	self ent_flag_clear( "coop_downed" );
	self notify( "revived" );
	player_coop_destroy_use_target();
	self thread player_dying_effect_remove();
	
	// so other player loses health bonus from being only mobile guy
	thread maps\_gameskill::resetSkill();
}

player_coop_proc_ended()
{
	self endon( "death" );

	flag_waitopen( "coop_revive" );
	self ent_flag_clear( "coop_proc_running" );
	self EnableDeathShield( false );
}

player_coop_set_down_attributes()
{
	self endon( "death" );

	// Use radius increased when someone is down... more like MP distance
	level._default_use_radius = getdvarint( "player_useradius" );
	setsaveddvar( "player_useradius", 128 );

	self.coop_downed = true;
	self.ignoreRandomBulletDamage = true;
	self EnableInvulnerability();
	self ent_flag_set( "coop_downed" );
	self.health = 2;
	self.maxhealth = self.original_maxhealth;
	self.ignoreme = true;

	self DisableUsability();
	self DisableWeaponSwitch();
	self disableoffhandweapons();
	self DisableWeapons();

	self thread player_coop_kill_by_vehicle();
	self thread player_coop_set_down_part1();
}

player_coop_kill_by_vehicle()
{
	self endon( "revived" );
	self endon( "death" );
	level endon( "special_op_terminated" );

	if ( flag( "special_op_terminated" ) )
	{
		return;
	}

	if ( !IsAlive( self ) )
	{
		return;
	}

	while ( 1 )
	{
		vehicles = Vehicle_GetArray();
		foreach ( vehicle in vehicles )
		{
			speed = vehicle Vehicle_GetSpeed();
			if ( abs( speed ) == 0 )
			{
				continue;
			}

			if ( self IsTouching( vehicle ) )
			{
				vehicle maps\_specialops::so_crush_player( self, "MOD_CRUSH" );
				return;
			}
		}

		wait( 0.05 );
	}
}

player_coop_set_original_attributes()
{
	// Use radius decreased to default when someone is revived.
	setsaveddvar( "player_useradius", level._default_use_radius );
	level._default_use_radius = undefined;

	self.ignoreRandomBulletDamage = false;
	self ent_flag_clear( "coop_downed" );
	self.down_part2_proc_ran = undefined;

	// This is done like this because when a guy goes down, he forces the *other*
	// person to turn on the health icon. Since only one guy can be down at a time,
	// we can trust that we only need to reset on the other player.
//	self CreateFriendlyHudIcon_Normal();
	other_player = get_other_player( self );
	other_player delaythread( 0.1, ::CreateFriendlyHudIcon_Normal );

	self player_coop_getup();

	self.health = self.maxhealth;
	self.ignoreme = false;
	self setstance( "stand" );

	self EnableUsability();
	self enableoffhandweapons();
	self EnableWeaponSwitch();
	// Don't enable weapons when placing a sentry.
	if ( !isdefined( self.placingSentry ) )
	{
		self EnableWeapons();
	}

	self.coop_downed = undefined;
	wait 1.0;
	self DisableInvulnerability();
}

// Could possibly be a useful utility function, but only useful here right now.
check_for_pistol()
{
	AssertEx( IsPlayer( self ), "check_for_pistol() was called on a non-player." );

	weapon_list = self GetWeaponsListPrimaries();
	foreach ( weapon in weapon_list )
	{
		// Need to account for Akimbo weapons?
		if ( WeaponClass( weapon ) == "pistol" )
			return weapon;
	}
		
	return undefined;
}

remove_pistol_if_extra()
{
	AssertEx( IsPlayer( self ), "remove_pistol_if_extra() was called on a non-player." );

	if ( isdefined( self.forced_pistol ) )
	{
		self takeweapon( self.forced_pistol );
		self.forced_pistol = undefined;
	}

	if ( player_can_restore_weapon( self.preincap_weapon ) )
	{
		self SwitchToWeapon( self.preincap_weapon );
	}
	else
	{
		primary_weapons = self GetWeaponsListPrimaries();
		assert( primary_weapons.size > 0 );
		self SwitchToWeapon( primary_weapons[0] );
	}
	self.preincap_weapon = undefined;
}

player_can_restore_weapon( weapon )
{
	if ( !isdefined( weapon ) )
		return false;

	if ( weapon == "none" )
		return false;

	if ( !self HasWeapon( weapon ) )
		return false;

	return true;
}

player_coop_force_switch_to_pistol()
{
	self.preincap_weapon = self GetCurrentWeapon();

	weapon_pistol = self check_for_pistol();

	if ( !isdefined( weapon_pistol ) )
	{
		weapon_pistol = "Beretta";
		if ( isdefined( level._coop_incap_weapon ) )
			weapon_pistol = level._coop_incap_weapon;

		self.forced_pistol = weapon_pistol;
		self giveWeapon( weapon_pistol );
	}

	self SwitchToWeapon( weapon_pistol );

	// Don't enable weapons when placing a sentry.
	if ( !isdefined( self.placingSentry ) )
	{
		self EnableWeapons();
	}
}

player_coop_set_down_part1()
{
	self endon( "revived" );

	self.laststand = true;
	wait .3;

	self player_coop_force_switch_to_pistol();

	wait .25;
	self DisableInvulnerability();

	self thread player_coop_down_draw_attention();

	self waittill( "damage" );
	self thread player_coop_set_down_part2();
}

player_coop_set_down_part2()
{
	self.down_part2_proc_ran = true;

	self disableweapons();
	self thread player_dying_effect();

	self.ignoreme = true;
	self.ignoreRandomBulletDamage = true;
	self EnableInvulnerability();
}

player_dying_effect()
{
	self endon( "death" );
	self endon( "revived" );

	//allow this thread to only be run once
	if ( !ent_flag_exist( "coop_dying_effect" ) )
		ent_flag_init( "coop_dying_effect" );
	else if ( ent_flag( "coop_dying_effect" ) )
		return;
	ent_flag_set( "coop_dying_effect" );

	for ( ;; )
	{
		self shellshock( "default", 60 );
		wait ( 60 );
	}
}

player_dying_effect_remove()
{
	if ( ent_flag_exist( "coop_dying_effect" ) )
		ent_flag_clear( "coop_dying_effect" );

	self stopShellShock();
}

player_coop_down_draw_attention()
{
	self endon( "death" );
	self endon( "revived" );
	self endon( "damage" );

	notifyoncommand( "draw_attention", "+attack" );
	self waittill_notify_or_timeout( "draw_attention", 4 );
	self.ignoreme = false;
	self.ignoreRandomBulletDamage = false;
}

player_coop_getup()
{
	self endon( "death" );

	self disableweapons();
	self remove_pistol_if_extra ();
	
	self.laststand = false;
	self.achieve_downed_kills = undefined;
	wait .3;
	//self player_recallweapons();
}

player_coop_kill()
{
	self ent_flag_set( "coop_is_dead" );
	self thread player_dying_effect_remove();

	if ( flag( "coop_fail_when_all_dead" ) )
		flag_wait( "coop_alldowned" );

	self EnableDeathShield( false );
	self DisableInvulnerability();
	self EnableHealthShield( false );
	self.laststand = false;
	self.achieve_downed_kills = undefined;
	waittillframeend;
	self kill();
}

player_coop_check_alldowned()
{
	foreach ( player in level._players )
	{
		downed = player ent_flag( "coop_downed" );
		if ( !downed )
			return;
	}

	flag_set( "coop_alldowned" );
}
