#include common_scripts\utility;
//#include maps\_hud_util;
#using_animtree( "sentry_gun" );

/*QUAKED script_model_pickup_sentry_gun (1 0 0) (-32 -16 0) (32 16 24) ORIENT_LOD NO_SHADOW  NO_STATIC_SHADOWS
defaultmdl="sentry_gun_folded"
default:"model" "sentry_gun_folded"
*/

/*QUAKED script_model_pickup_sentry_minigun (1 0 0) (-32 -16 0) (32 16 24) ORIENT_LOD NO_SHADOW  NO_STATIC_SHADOWS
defaultmdl="sentry_minigun_folded"
default:"model" "sentry_minigun_folded"
*/

/*
code support:
-physics on turrets

todo:
-make hint print while in placement mode
-hit max number of turrets at 32, but I could limit the number allowed
-get behind turret and change team
*/

/*
	Constants
*/

// default
sentry_updateTime 				 = 0.05;
shielded_sentry_health 			 = 350;// direct hit from an RPG
shielded_sentry_bullet_armor 	 = 2000;
minigun_sentry_health 			 = 190;// frag grenade does 200 inner damage
minigun_sentry_bullet_armor 	 = 1200;
minigun_sentry_bullet_armor_enemy 	 = 0;

// mp
shielded_sentry_bullet_armor_mp	 = 300;
minigun_sentry_bullet_armor_mp	 = 300;

sentry_mode_name_on				 = "sentry";
sentry_mode_name_off			 = "sentry_offline";

main()
{
	precacheModel( "sentry_minigun" );
	precacheModel( "sentry_minigun_obj" );
	precacheModel( "sentry_minigun_obj_red" );
	precacheModel( "sentry_minigun_folded_obj" );
	precacheModel( "sentry_minigun_destroyed" );
	precacheModel( "sentry_gun" );
	precacheModel( "sentry_gun_obj" );
	precacheModel( "sentry_gun_obj_red" );
	precacheModel( "sentry_gun_folded_obj" );
	precacheModel( "sentry_gun_destroyed" );
	precacheModel( "tag_laser" );

	if ( isSP() )
	{
		precacheTurret( "sentry_gun" );
		precacheTurret( "sentry_minigun" );
		precacheTurret( "sentry_minigun_enemy" );
	}
	else
	{
		precacheTurret( "sentry_gun_mp" );
		precacheTurret( "sentry_minigun_mp" );
	}

	// LANG_ENGLISH		Press and hold ^3&&1^7 to move the turret."
	precacheString( &"SENTRY_MOVE" );
	// Press and hold ^3&&1^7 to pick up the turret.
	precacheString( &"SENTRY_PICKUP" );
	precacheString( &"SENTRY_PLACE" );
	precacheString( &"SENTRY_CANNOT_PLACE" );

	level._effect[ "sentry_turret_overheat_smoke_sp" ]		= loadfx( "smoke/sentry_turret_overheat_smoke_sp" );
	level._effect[ "sentry_turret_explode" ]				 = loadfx( "explosions/sentry_gun_explosion" );
	level._effect[ "sentry_turret_explode_smoke" ]			 = loadfx( "smoke/car_damage_blacksmoke" );

	level._sentry_settings = [];

	level._sentry_settings[ "sentry_gun" ] 					 = spawnStruct();
	sentry_gun_default_settings( "sentry_gun" );

	level._sentry_settings[ "sentry_minigun" ] 				 = spawnStruct();
	sentry_minigun_default_settings( "sentry_minigun" );

	if ( isSP() )
	{
		// sentry overheat override settings
		level._sentry_overheating_speed = 1; // 1 heat points per second
		level._sentry_cooling_speed = 1; // 1 heat points cooling per second
		
		if ( !isdefined( level._sentry_fire_time ) )
			level._sentry_fire_time = 8; // seconds of continous fire ( aka heat points )
		if ( !isdefined( level._sentry_cooldown_time ) )
			level._sentry_cooldown_time = 4; // seconds of continous fire ( aka heat points )
	}

	level._sentryTurretSettings[ "easy" ][ "convergencePitchTime" ] 	= 2.5;
	level._sentryTurretSettings[ "easy" ][ "convergenceYawTime" ] 	= 2.5;
	level._sentryTurretSettings[ "easy" ][ "suppressionTime" ] 		= 3.0;
	level._sentryTurretSettings[ "easy" ][ "aiSpread" ] 				= 2.0;
	level._sentryTurretSettings[ "easy" ][ "playerSpread" ] 			= 0.5;

	// for pre-placed guns
	guns = getentarray( "sentry_gun", "targetname" );
	mini_guns = getentarray( "sentry_minigun", "targetname" );
	foreach( gun in guns )
	{
		gun sentry_init( undefined, "sentry_gun" );
	}
	
	foreach( minigun in mini_guns )
	{
		minigun sentry_init( undefined, "sentry_minigun" );
	}

	array_thread( getentarray( "script_model_pickup_sentry_gun", "classname" ), ::sentry_pickup_init, "sentry_gun" );
	array_thread( getentarray( "script_model_pickup_sentry_minigun", "classname" ), ::sentry_pickup_init, "sentry_minigun" );
}

sentry_gun_default_settings( type )
{
	level._sentry_settings[ type ].burst_shots_min 	 = 10;
	level._sentry_settings[ type ].burst_shots_max 	 = 35;
	level._sentry_settings[ type ].burst_pause_min 	 = 0.2;
	level._sentry_settings[ type ].burst_pause_max 	 = 0.8;
	level._sentry_settings[ type ].model 			 = "sentry_gun";
	level._sentry_settings[ type ].destroyedModel 	 = "sentry_gun_destroyed";
	level._sentry_settings[ type ].pickupModel 		 = "sentry_gun_folded";
	level._sentry_settings[ type ].pickupModelObj 	 = "sentry_gun_folded_obj";
	level._sentry_settings[ type ].placementmodel 	 = "sentry_gun_obj";
	level._sentry_settings[ type ].placementmodelfail = "sentry_gun_obj_red";
	level._sentry_settings[ type ].health 			 = shielded_sentry_health;

	if ( isSP() )
	{
		level._sentry_settings[ type ].damage_smoke_time = 15;
		level._sentry_settings[ type ].weaponInfo 		 = "sentry_gun";
		level._sentry_settings[ type ].targetname 		 = "sentry_gun";
	}
	else
	{
		level._sentry_settings[ type ].damage_smoke_time = 5;
		level._sentry_settings[ type ].weaponInfo 		 = "sentry_gun_mp";
		level._sentry_settings[ type ].targetname 		 = "sentry_gun_mp";
	}
}

sentry_minigun_default_settings( type )
{
	level._sentry_settings[ type ].burst_shots_min 	 = 20;
	level._sentry_settings[ type ].burst_shots_max 	 = 60;
	level._sentry_settings[ type ].burst_pause_min 	 = 0.5;
	level._sentry_settings[ type ].burst_pause_max 	 = 1.3;
	level._sentry_settings[ type ].model 			 = "sentry_minigun";
	level._sentry_settings[ type ].destroyedModel 	 = "sentry_minigun_destroyed";
	level._sentry_settings[ type ].pickupModel 		 = "sentry_minigun_folded";
	level._sentry_settings[ type ].pickupModelObj 	 = "sentry_minigun_folded_obj";
	level._sentry_settings[ type ].placementmodel 	 = "sentry_minigun_obj";
	level._sentry_settings[ type ].placementmodelfail = "sentry_minigun_obj_red";
	level._sentry_settings[ type ].health 			 = minigun_sentry_health;

	if ( isSP() )
	{
		level._sentry_settings[ type ].damage_smoke_time = 15;
		level._sentry_settings[ type ].anim_loop 		 = %minigun_spin_loop;
		level._sentry_settings[ type ].weaponInfo 		 = "sentry_minigun";
		level._sentry_settings[ type ].targetname 		 = "sentry_minigun";
	}
	else
	{
		level._sentry_settings[ type ].damage_smoke_time = 5;
		level._sentry_settings[ type ].weaponInfo 		 = "sentry_minigun_mp";
		level._sentry_settings[ type ].targetname 		 = "sentry_minigun_mp";
	}
}

sentry_pickup_init( sentryType )
{
	assert( isdefined( sentryType ) );
	assert( isdefined( level._sentry_settings[ sentryType ] ) );
	self setModel( self.model );
	self.sentryType = sentryType;

	self setCursorHint( "HINT_NOICON" );
	// Press and hold ^3&&1^7 to pick up the turret.
	self setHintString( &"SENTRY_PICKUP" );
	self makeUsable();

	self thread folded_sentry_use_wait( sentryType );
}

giveSentry( sentryType )
{
	assert( isdefined( level._sentry_settings ) );
	assert( isdefined( level._sentry_settings[ sentryType ] ) );

	self.last_sentry = sentryType;
	self thread spawn_and_place_sentry( sentryType );
}

sentry_init( team, sentryType, owner )
{
	if ( !isdefined( team ) )
	{
		assert( isdefined( self.script_team ) );
		if ( !isdefined( self.script_team ) )
			self.script_team = "axis";
		team = self.script_team;
	}
	
	assert( isDefined( team ) );
	assert( isDefined( sentryType ) );
	
	self setTurretModeChangeWait( true );
	self makeSentrySolid();
	self makeTurretInoperable();
	self SentryPowerOn();
	self setCanDamage( true );
	self setDefaultDropPitch( -89.0 );	// setting this mainly prevents Turret_RestoreDefaultDropPitch() from running
	
	if ( isSP() || level._teambased )
		self setTurretTeam( team );

	self.sentryType = sentryType;
	self.isSentryGun = true;
	self.kill_reward_money = 350;
	self.kill_melee_reward_money = 400;
	self.sentry_battery_timer = 60;	// sec

	//bullet armor acts as an extra pool of health for bullet damage. 
	//once its removed bullet damage affects the sentry like other kinds of damage.
	if ( isSP() )
	{
		if ( self.weaponinfo == "sentry_gun" )// sentry_minigun and sentry_minigun_enemy get the same settings
			self.bullet_armor = shielded_sentry_bullet_armor;
		else
		{
			self.bullet_armor = minigun_sentry_bullet_armor;
		}
	}
	else
	{
		if ( self.weaponinfo == "sentry_gun" )
			self.bullet_armor = shielded_sentry_bullet_armor_mp;
		else
			self.bullet_armor = minigun_sentry_bullet_armor_mp;
	}

	if ( isSP() )
	{
		self call [[ level._makeEntitySentient_func ]]( team );
		self self_func( "useanimtree", #animtree );
		if ( isdefined( self.script_team ) && self.script_team == "axis" )
			self thread enemy_sentry_difficulty_settings();
	}

	self.health = level._sentry_settings[ sentryType ].health;

	self sentry_badplace_create();
	self thread sentry_beep_sounds();
	self thread sentry_enemy_wait();
	self thread sentry_death_wait();
	if ( !isSP() )
	{
		self thread sentry_emp_wait();
		self thread sentry_emp_damage_wait();
	}
	self thread sentry_health_monitor();
	self thread sentry_player_use_wait();
	
	if ( !isdefined( owner ) )
	{
		if( isSP() )
			owner = level._player;
	}
	assert( isdefined( owner ) );
	self sentry_set_owner( owner );
	self thread sentry_destroy_on_owner_leave( owner );
	
	if ( !isdefined( self.damage_functions ) )
		self.damage_functions = [];

	if ( getdvar( "money_enable", "0" ) == "1" && self.team == "axis" )
	{
		if ( isdefined( level._sentry_money_init_func ) )
			self thread [[ level._sentry_money_init_func ]]();
	}
}

sentry_death_wait()
{
	self endon( "deleted" );
	
	//self waittill_player_or_sentry_death();
	self waittill( "death", attacker, cause );

	if ( isdefined( level._stat_track_kill_func ) && isdefined( attacker ) )
		attacker [[ level._stat_track_kill_func ]]( self, cause );

	if ( !isSP() )
	{
		self removeFromTurretList();
		self thread sentry_place_mode_reset();
	}

	self thread sentry_burst_fire_stop();

	if ( isdefined( level._laserOff_func ) )
		self call [[ level._laserOff_func ]]();

	assert( isdefined( level._sentry_settings[ self.sentryType ] ) );
	assert( isdefined( level._sentry_settings[ self.sentryType ].destroyedModel ) );
	self setmodel( level._sentry_settings[ self.sentryType ].destroyedModel );
	self SentryPowerOff();
	
	if ( isSP() )
		self call [[ level._freeEntitySentient_func ]]();

	if ( !isSP() &&  isDefined( attacker ) && isPlayer( attacker ) )
	{
		if ( isDefined( self.owner ) )
			self.owner thread [[level._leaderDialogOnPlayer_func]]( "destroy_sentry", "sentry_status" );
		attacker thread [[ level._onXPEvent ]]( "kill" );
	}

	self setSentryCarried( false );
	self SetCanDamage( true );
	self.ignoreMe = false;
	self makeUnusable();
	self SetSentryOwner( undefined );
	self SetTurretMinimapVisible( false );
	self playsound( "sentry_explode" );
	playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" );
	
	if ( isSP() )
		self setContents( 0 );
	
	wait 1.5;
	self playsound( "sentry_explode_smoke" );
	timeToSteam = level._sentry_settings[ self.sentryType ].damage_smoke_time * 1000;
	startTime = getTime();
	for ( ;; )
	{
		playfxOnTag( getfx( "sentry_turret_explode_smoke" ), self, "tag_aim" );
		wait .4;
		if ( getTime() - startTime > timeToSteam )
			break;
	}

	if ( !isSP() )
		self thread removeDeadSentry();
}

handle_sentry_on_carrier_death( sentry )
{
	level endon( "game_ended" );
	self endon( "sentry_placement_finished" );
	self waittill( "death" );

	if ( isSp() )
	{
		sentry notify( "death" );
		return;
	}

	if ( !self.canPlaceEntity )
	{	
		sentry sentry_place_mode_reset();
		sentry notify( "deleted" );
		
		waittillframeend;
		sentry delete();
		
		return;
	}
	
	if ( !isSp() )
	{	
		self thread place_sentry( sentry );
	}
	
}

kill_sentry_on_carrier_disconnect( sentry )
{
	level endon( "game_ended" );
	self endon( "sentry_placement_finished" );
	self waittill( "disconnect" );

	sentry notify( "death" );
}

sentry_player_use_wait()
{
	level endon( "game_ended" );
	self endon( "death" );
	
	assert( isDefined( self.sentryType ) );

	if ( self.health <= 0 )
		return;

	for ( ;; )
	{
		self waittill( "trigger", player );

		if ( isDefined( player.placingSentry ) )
			continue;

		// only owner of sentry can move sentry in MP
		if ( !isSP() )
		{
			// Checked through code now; Assert left for reference.
			assert( isDefined( self.owner ) );
			assert( player == self.owner );
		}

		break;
	}

	player thread handle_sentry_on_carrier_death( self );
	player thread kill_sentry_on_carrier_disconnect( self );
	player thread sentry_placement_endOfLevel_cancel_monitor( self );

	if ( !isSP() && !isAlive( player ) )
		return;
		
	if ( !isSP() )
		self sentry_team_hide_icon();
		
	self SentryPowerOff();// makes the turret non - operational while being moved
	player.placingSentry = self;
	self setSentryCarried( true );
	self.ignoreMe = true;
	self SetCanDamage( false );
	
	player _disableWeapon();
	//player _disableUsability();
	self makeSentryNotSolid();
	self sentry_badplace_delete();
	player thread move_sentry_wait( self );
	player thread updateSentryPositionThread( self );
}

sentry_badplace_create()
{
	if ( !isSP() )
		return;
	self.badplace_name = "" + getTime();
	call [[ level._badplace_cylinder_func ]]( self.badplace_name, 0, self.origin, 32, 128, self.team, "neutral" );
}

sentry_badplace_delete()
{
	if ( !isSP() )
		return;
	assert( isdefined( self.badplace_name ) );
	call [[ level._badplace_delete_func ]]( self.badplace_name );
	self.badplace_name = undefined;
}

move_sentry_wait( sentry )
{
	level endon( "game_ended" );
	sentry endon( "death" );
	sentry endon( "deleted" );

	self endon( "death" );
	self endon( "disconnect" );
	assert( isdefined( sentry ) );

	for ( ;; )
	{
		//debounce
		self waitActivateButton( false );

		// wait for button press
		self waitActivateButton( true );

		updateSentryPosition( sentry );
		if ( self.canPlaceEntity )
			break;
	}
	
	place_sentry( sentry );
}

place_sentry( sentry )
{
	if ( !isSP() )
	{
		self endon( "death" );
		level endon( "end_game" );
	}
	
	self.placingSentry = undefined;
	sentry setSentryCarried( false );
	sentry SetCanDamage( true );
	sentry.ignoreMe = false;
	
	if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) )
	{
		self _enableWeapon();
		//self _enableUsability();
	}
	else
	{
		// Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon()
		// This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to 
		// still keep count as if they did get re-enabled so that the next time you place a turret it works.
		self.disabledWeapon--;
	}
	
	sentry makeSentrySolid();
	sentry setmodel( level._sentry_settings[ sentry.sentryType ].model );
	sentry sentry_badplace_create();
	assert( isdefined( sentry.contents ) );
	sentry setContents( sentry.contents );
	self notify( "sentry_placement_finished", sentry );
	
	sentry notify( "sentry_carried" );
	sentry.overheated = false;
	self sentry_placement_hint_hide();
	
	if ( !isSP() )
		sentry sentry_team_show_icon();
		
	sentry SentryPowerOn();
	thread play_sound_in_space( "sentry_gun_plant", sentry.origin );
	
	//debounce	
	self waitActivateButton( false );
	sentry thread sentry_player_use_wait();	
}

sentry_enemy_wait()
{
	level endon( "game_ended" );
	self endon( "death" );
	self thread sentry_overheat_monitor();
	
	for ( ;; )
	{
		self waittill_either( "turretstatechange", "cooled" );

		if ( self isFiringTurret() )
		{
			self thread sentry_burst_fire_start();
			if ( isdefined( level._laserOn_func ) )
				self call [[ level._laserOn_func ]]();
		}
		else
		{
			self thread sentry_burst_fire_stop();
			if ( isdefined( level._laserOff_func ) )
				self call [[ level._laserOff_func ]]();
		}
	}
}

// Sentry overheat behavoir for SP ====================================================
// Note: 	To enable for mp, take out the isSP() check in main() function for overheat override variables.
//				However, there might be some unseen behavoiral conflicts with battery timer, currently SP only - Julian

// Note 2:  Turrets now have a code ersion of doing this, we probably don't want to mix and match both, so this should be 
//			cleaned up / removed after MW2
sentry_overheat_monitor()
{
	self endon( "death" );
	
	assert( isDefined( self ) );
	assert( isDefined( self.sentryType ) );
	if ( self.sentryType != "sentry_minigun" )
		return;
	
	if ( !isdefined( level._sentry_overheating_speed ) )
		return;
	
	self.overheat = 0;
	self.overheated = false;
	
	if ( getdvarint( "sentry_overheat_debug" ) == 1 )
		self thread sentry_overheat_debug();

	while ( true )
	{
		if ( self.overheat >= ( level._sentry_fire_time * 10 ) )
		{
			self thread sentry_overheat_deactivate();
			self waittill_either( "cooled", "sentry_carried" );
		}

		if ( self IsFiringTurret() )
		{
			self.overheat += 1;
		}
		else
		{
			if ( self.overheat > 0 )
				self.overheat -= 1;
		}
		
		wait 0.1/level._sentry_overheating_speed;
	}
}

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

	while ( self.overheated )
	{
		if ( self.overheat > 0 )
			self.overheat -= 1;
		
		wait 0.1/level._sentry_overheating_speed;
	}
}

sentry_overheat_debug()
{
	self endon( "death" );
	while( true )
	{
		overheat_value = self.overheat / (level._sentry_fire_time*10);
		overheat_print_l = "[ ";
		overheat_print_r = " ]";
		if( self.overheated ) 
		{
			overheat_print_l = "{{{ ";
			overheat_print_r = " }}}";
		}
		
		print3d( self.origin + ( 0,0,45 ), overheat_print_l + self.overheat + " / " + level._sentry_fire_time*10 + overheat_print_r, ( 0+overheat_value, 1-overheat_value, 1-overheat_value ), 1, 0.35, 4 );
		wait 0.2;
	}
}

sentry_overheat_deactivate()
{
	self endon( "death" );
	
	self notify( "overheated" );
	self.overheated = true;
	self sentry_burst_fire_stop();
	
	self thread sentry_overheat_reactivate();
}

sentry_overheat_reactivate()
{
	self endon( "death" );
	self endon( "sentry_carried" );
	
	self thread sentry_cooling();
	
	wait level._sentry_cooldown_time;
	self notify( "cooled" );
	self.overheat = 0;
	self.overheated = false;
}

// END of sentry overheat behavoir for SP =================================================

sentry_burst_fire_start()
{
	self endon( "death" );
	level endon( "game_ended" );

	if ( isdefined( self.overheated ) && self.overheated )
		return;
	
	self thread fire_anim_start();

	self endon( "stop_shooting" );
	self notify( "shooting" );

	assert( isdefined( self.weaponinfo ) );
	fireTime = weaponFireTime( self.weaponinfo );
	assert( isdefined( fireTime ) && fireTime > 0 );

	for ( ;; )
	{		
		self turret_start_anim_wait();
		numShots = randomintrange( level._sentry_settings[ self.sentryType ].burst_shots_min, level._sentry_settings[ self.sentryType ].burst_shots_max );
		for ( i = 0 ; i < numShots ; i++ )
		{
			if ( self canFire() )
				self shootTurret();

			wait fireTime;
		}
		wait randomfloatrange( level._sentry_settings[ self.sentryType ].burst_pause_min, level._sentry_settings[ self.sentryType ].burst_pause_max );
	}
}

sentry_allowFire( bAllow, timeOut )
{
	self notify( "allowFireThread" );
	self endon( "allowFireThread" );
	self endon( "death" );

	self.taking_damage = bAllow;

	if ( isdefined( timeOut ) && !bAllow )
	{
		wait timeOut;
		if ( isdefined( self ) )
			self thread sentry_allowFire( true );
	}
}

canFire()
{
	if ( !isdefined( self.taking_damage ) )
		return true;

	return self.taking_damage;
}

sentry_burst_fire_stop()
{
	self thread fire_anim_stop();
	self notify( "stop_shooting" );
	self thread sentry_steam();
}

sentry_steam()
{
	self endon( "shooting" );
	self endon( "deleted" );

	wait randomfloatrange( 0.0, 1.0 );

	timeToSteam = 6 * 1000;
	startTime = getTime();
	
	// temp sound fx
	if ( isdefined( self ) )
		self playsound( "sentry_steam" );
		
	while ( isdefined( self ) )
	{
		playfxOnTag( getfx( "sentry_turret_overheat_smoke_sp" ), self, "tag_flash" );
		wait .3;
		if ( getTime() - startTime > timeToSteam )
			break;
	}
}

turret_start_anim_wait()
{
	if ( isdefined( self.allow_fire ) && self.allow_fire == false )
		self waittill( "allow_fire" );
}

fire_anim_start()
{
	self notify( "anim_state_change" );
	self endon( "anim_state_change" );
	self endon( "stop_shooting" );
	self endon( "deleted" );
	level endon( "game_ended" );
	self endon( "death" );

	if ( !isdefined( level._sentry_settings[ self.sentryType ].anim_loop ) )
		return;

	self.allow_fire = false;

	//ramp up the animation from 0.1 speed to 1.0 speed over time
	if ( !isdefined( self.momentum ) )
		self.momentum = 0;

	self thread fire_sound_spinup();
	for ( ;; )
	{
		if ( self.momentum >= 1.0 )
			break;
		self.momentum += 0.1;
		self.momentum = cap_value( self.momentum, 0.0, 1.0 );
		if ( isSP() )
			self self_func( "setanim", level._sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum );
		wait 0.2;
	}
	self.allow_fire = true;
	self notify( "allow_fire" );
}

delete_sentry_turret()
{
	self notify( "deleted" );
	wait .05;
	self notify( "death" );

	if ( isDefined( self.obj_overlay ) )
		self.obj_overlay delete();

	if ( isDefined( self.cam ) )
		self.cam delete();
		
	self delete();
}

fire_anim_stop()
{
	self notify( "anim_state_change" );
	self endon( "anim_state_change" );

	if ( !isdefined( level._sentry_settings[ self.sentryType ].anim_loop ) )
		return;

	self thread fire_sound_spindown();

	self.allow_fire = false;

	for ( ;; )
	{
		if ( !isdefined( self.momentum ) )
			break;
		if ( self.momentum <= 0.0 )
			break;
		self.momentum -= 0.1;
		self.momentum = cap_value( self.momentum, 0.0, 1.0 );
		if ( isSP() )
			self self_func( "setanim", level._sentry_settings[ self.sentryType ].anim_loop, 1.0, 0.2, self.momentum );
		wait 0.2;
	}
}

fire_sound_spinup()
{
	self notify( "sound_state_change" );
	self endon( "sound_state_change" );
	self endon( "deleted" );

	if ( self.momentum < 0.25 )
	{
		self playsound( "sentry_minigun_spinup1" );
		wait 0.6;
		self playsound( "sentry_minigun_spinup2" );
		wait 0.5;
		self playsound( "sentry_minigun_spinup3" );
		wait 0.5;
		self playsound( "sentry_minigun_spinup4" );
		wait 0.5;
	}
	else
	if ( self.momentum < 0.5 )
	{
		self playsound( "sentry_minigun_spinup2" );
		wait 0.5;
		self playsound( "sentry_minigun_spinup3" );
		wait 0.5;
		self playsound( "sentry_minigun_spinup4" );
		wait 0.5;
	}
	else
	if ( self.momentum < 0.75 )
	{
		self playsound( "sentry_minigun_spinup3" );
		wait 0.5;
		self playsound( "sentry_minigun_spinup4" );
		wait 0.5;
	}
	else
	if ( self.momentum < 1 )
	{
		self playsound( "sentry_minigun_spinup4" );
		wait 0.5;
	}

	thread fire_sound_spinloop();
}

fire_sound_spinloop()
{
	self endon( "death" );
	self notify( "sound_state_change" );
	self endon( "sound_state_change" );

	while ( 1 )
	{
		self playsound( "sentry_minigun_spin" );
		wait 2.5;
	}
}

fire_sound_spindown()
{
	self notify( "sound_state_change" );
	self endon( "sound_state_change" );
	self endon( "deleted" );

	if ( !isdefined( self.momentum ) )
		return;

	if ( self.momentum > 0.75 )
	{
		self stopsounds();
		self playsound( "sentry_minigun_spindown4" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown3" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown2" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown1" );
		wait 0.65;
	}
	else
	if ( self.momentum > 0.5 )
	{
		self playsound( "sentry_minigun_spindown3" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown2" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown1" );
		wait 0.65;
	}
	else
	if ( self.momentum > 0.25 )
	{
		self playsound( "sentry_minigun_spindown2" );
		wait 0.5;
		self playsound( "sentry_minigun_spindown1" );
		wait 0.65;
	}
	else
	{
		self playsound( "sentry_minigun_spindown1" );
		wait 0.65;
	}
}

sentry_beep_sounds()
{
	self endon( "death" );
	for ( ;; )
	{
		wait randomfloatrange( 3.5, 4.5 );
		self thread play_sound_in_space( "sentry_gun_beep", self.origin + ( 0, 0, 40 ) );
	}
}

spawn_and_place_sentry( sentryType )
{
	level endon( "game_ended" );

	assert( self.classname == "player" );
	assert( isdefined( sentryType ) );
	assert( isdefined( level._sentry_settings[ sentryType ] ) );
	assert( isdefined( level._sentry_settings[ sentryType ].placementmodel ) );
	assert( isdefined( level._sentry_settings[ sentryType ].placementmodelfail ) );	

	if ( isdefined( self.placingSentry ) )
		return;
	
	self _disableWeapon();
	//self _disableUsability();
	self notify( "placingSentry" );
	
	assert( isdefined( level._sentry_settings[ sentryType ] ) );
	assert( isdefined( level._sentry_settings[ sentryType ].weaponInfo ) );
	assert( isdefined( level._sentry_settings[ sentryType ].model ) );
	assert( isdefined( level._sentry_settings[ sentryType ].targetname ) );
	
	sentry_gun = spawnTurret( "misc_turret", self.origin, level._sentry_settings[ sentryType ].weaponInfo );
	sentry_gun setmodel( level._sentry_settings[ sentryType ].placementModel );
	sentry_gun.weaponinfo = level._sentry_settings[ sentryType ].weaponInfo;    
    sentry_gun.targetname = level._sentry_settings[ sentryType ].targetname;
    sentry_gun.weaponName = level._sentry_settings[ sentryType ].weaponInfo;
    sentry_gun.angles = self.angles;
	sentry_gun.team = self.team;
	sentry_gun.attacker = self;
	sentry_gun.sentryType = sentryType;

	sentry_gun makeTurretInoperable();
	sentry_gun sentryPowerOff();
	sentry_gun setCanDamage( false );
	sentry_gun sentry_set_owner( self );
	sentry_gun setDefaultDropPitch( -89.0 );	// setting this mainly prevents Turret_RestoreDefaultDropPitch() from running

	self.placingSentry = sentry_gun;
	sentry_gun setSentryCarried( true );
	sentry_gun SetCanDamage( false );
	sentry_gun.ignoreMe = true;

	if ( !isSP() )
		sentry_gun addToTurretList();
	
	// wait to delete the sentry when cancelled
	self thread sentry_placement_cancel_monitor( sentry_gun );
	
	// wait to delete the sentry on end of level
	self thread sentry_placement_endOfLevel_cancel_monitor( sentry_gun );

	// wait until the player plants the sentry
	self thread sentry_placement_initial_wait( sentry_gun );

	// keep the indicator model positioned with traces forever until the thread is ended
	self thread updateSentryPositionThread( sentry_gun );
	
	// wait until the turret placement has been finished or canceled
	if ( !isSP() )
		self waittill_any( "sentry_placement_finished", "sentry_placement_canceled", "death" );
	else
		self waittill_any( "sentry_placement_finished", "sentry_placement_canceled" );

	self sentry_placement_hint_hide();

	if ( !maps\_utility::is_coop() || !maps\_utility::is_player_down_and_out( self ) )
	{
		self _enableWeapon();
		//self _enableUsability();
	}
	else
	{
		// Manually decrease the disabledWeapon count because we didn't actually call _enableWeapon()
		// This is necessary because co-op revive needs to prevent the enable from happening, but sentries need to 
		// still keep count as if they did get re-enabled so that the next time you place a turret it works.
		self.disabledWeapon--;
	}
	
	self.placingSentry = undefined;
	sentry_gun setSentryCarried( false );
	self SetCanDamage( true );
	sentry_gun.ignoreMe = false;
}


sentry_placement_cancel_monitor( sentry_gun )
{
	self endon ( "sentry_placement_finished" );
	
	if ( !isSP() )
		self waittill_any( "sentry_placement_canceled", "death", "disconnect");
	else
		self waittill_any( "sentry_placement_canceled" );
	
	waittillframeend;
	sentry_gun delete();
}

sentry_placement_endOfLevel_cancel_monitor( sentry_gun )
{
	self endon ( "sentry_placement_finished" );
	
	if ( isSP() )
		return;
			
	level waittill( "game_ended" );
	
	if ( !isDefined( sentry_gun ) )
		return;
	
	//sentry_gun notify( "deleted" );
	if ( !self.canPlaceEntity )
	{	
		sentry_gun notify( "deleted" );
		
		waittillframeend;
		sentry_gun delete();
		return;
	}

	self thread place_sentry( sentry_gun );
}


sentry_restock_wait()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "restock_reset" );

	// Cancel/restock on death or when toggling the killstreak
	self notifyOnPlayerCommand( "cancel sentry", "+actionslot 4" );
	self waittill_any( "death", "cancel sentry" );
	assert( isdefined( self.last_sentry ) );
	
	self notify( "sentry_placement_canceled" );
}


sentry_placement_initial_wait( sentry_gun )
{
	level endon( "game_ended" );

	self endon( "sentry_placement_canceled" );

	if ( !isSP() )
	{
		self endon( "disconnect" );
		//self endon( "death" );
		sentry_gun thread sentry_reset_on_owner_death();
		self thread sentry_restock_wait();
	}

	//debounce from picking up the gun
	while ( self useButtonPressed() )
		wait 0.05;

	for ( ;; )
	{
		// couldn't place entity so wait until the buttons are unpressed before trying again
		self waitActivateButton( false );

		// wait until the button is pressed
		self waitActivateButton( true );

		updateSentryPosition( sentry_gun );
		if ( self.canPlaceEntity )
			break;
	}

	if ( !isSP() ) //&& isAlive( self ) )
		self notify( "restock_reset" );

	if ( !isSP() )
	{
		sentry_gun.lifeId = self.lifeId;
		self sentry_team_setup( sentry_gun );
	}

	thread play_sound_in_space( "sentry_gun_plant", sentry_gun.origin );

	assert( isdefined( self.team ) );
	sentry_gun setmodel( level._sentry_settings[ sentry_gun.sentryType ].model );
	sentry_gun thread sentry_init( self.team, sentry_gun.sentryType, self );

	self notify( "sentry_placement_finished", sentry_gun );
	waittillframeend;	// wait so self.placingSentry can get cleared before notifying script that we can give the player another turret

	if ( !isSP() )
		sentry_gun thread sentry_die_on_batteryout();
}

updateSentryPositionThread( sentry_entity )
{
	level endon( "game_ended" );

	sentry_entity notify( "sentry_placement_started" );
	self endon( "sentry_placement_canceled" );
	self endon( "sentry_placement_finished" );

	sentry_entity endon( "death" );
	sentry_entity endon( "deleted" );

	if ( !isSP() )
	{
		self endon( "disconnect" );
		self endon( "death" );
	}

	for ( ;; )
	{
		updateSentryPosition( sentry_entity );		
		wait sentry_updateTime;
	}
}

updateSentryPosition( sentry_entity )
{
	placement = self canPlayerPlaceSentry();
	sentry_entity.origin = placement[ "origin" ];
	sentry_entity.angles = placement[ "angles" ];		
	self.canPlaceEntity = self isonground() && placement[ "result" ];
	self sentry_placement_hint_show( self.canPlaceEntity );

	if ( self.canPlaceEntity )
		sentry_entity setModel( level._sentry_settings[ sentry_entity.sentryType ].placementmodel );
	else
		sentry_entity setModel( level._sentry_settings[ sentry_entity.sentryType ].placementmodelfail );
}

sentry_placement_hint_show( hint_valid )
{
	assert( isDefined( self ) );
	assert( isDefined( hint_valid ) );
	
	// return if not changed
	if ( isdefined( self.forced_hint ) && (self.forced_hint == hint_valid) )
		return;

	self.forced_hint = hint_valid;

	if ( self.forced_hint )
		self ForceUseHintOn( &"SENTRY_PLACE" );
	else
		self ForceUseHintOn( &"SENTRY_CANNOT_PLACE" );
}

sentry_placement_hint_hide()
{
	assert( isDefined( self ) );
	
	// return if hidden already
	if ( !isdefined( self.forced_hint ) )
		return;

	self ForceUseHintOff();
	self.forced_hint = undefined;	
}

folded_sentry_use_wait( sentryType )
{
	// spawn another copy of the model so that it's not translucent
	self.obj_overlay = spawn( "script_model", self.origin );
	self.obj_overlay.angles = self.angles;
	self.obj_overlay setModel( level._sentry_settings[ sentryType ].pickupModelObj );

	for ( ;; )
	{
		self waittill( "trigger", player );

		if ( !isdefined( player ) )
			continue;

		if ( isDefined( player.placingSentry ) )
			continue;

		if ( !isSP() )
		{
			assert( isdefined( self.owner ) );
			if ( player != self.owner )
				continue;
		}

		break;
	}

	self thread play_sound_in_space( "sentry_pickup" );
	self.obj_overlay delete();
	self delete();

	// put the player into placement mode
	player thread spawn_and_place_sentry( sentryType );
}

sentry_health_monitor()
{
	self.healthbuffer = 20000;
	self.health += self.healthbuffer;
	self.currenthealth = self.health;
	attacker = undefined;
	type = undefined;

	while ( self.health > 0 )
	{
		self waittill( "damage", amount, attacker, direction_vec, point, type, modelName, tagName );

		if ( !isSP() && isdefined( attacker ) && isplayer( attacker ) && attacker sentry_attacker_is_friendly( self ) )
		{
			self.health = self.currenthealth;
			return;
		}

		if ( isdefined( level._stat_track_damage_func ) && isdefined( attacker ) )
			attacker [[ level._stat_track_damage_func ]]();

		assertex( isdefined( level._func[ "damagefeedback" ] ), "damagefeedback display function is undefined" );
		if ( isdefined( attacker ) && isplayer( attacker ) )
		{
			if ( !isSP() )
				attacker [[ level._func[ "damagefeedback" ] ]]( "false" );
			/* no more hit indicator in SP, commenting this out and replacing with the line above for MP only
			if ( isSP() )
				attacker [[ level.func[ "damagefeedback" ] ]]( self );
			else
				attacker [[ level.func[ "damagefeedback" ] ]]( "false" );
			*/
			self thread sentry_allowFire( false, 2.0 );
		}

		if ( self sentry_hit_bullet_armor( type ) )
		{
			//damage was to bullet armor, restore health and decrement bullet armor.
			self.health = self.currenthealth;
			self.bullet_armor -= amount;
		}
		else
			self.currenthealth = self.health;

		if ( self.health < self.healthbuffer )
			break;
	}

	if ( !isSP() &&  attacker sentry_attacker_can_get_xp( self ) )
		attacker thread [[ level._onXPEvent ]]( "kill" );

	self notify( "death", attacker, type );
}

sentry_hit_bullet_armor( type )
{
	if ( self.bullet_armor <= 0 )
		return false;
	if ( !( isdefined( type ) ) )
		return false;
	if ( ! issubstr( type, "BULLET" ) )
		return false;
	else
		return true;
}

enemy_sentry_difficulty_settings()
{
	difficulty = "easy";
	
	self SetConvergenceTime( level._sentryTurretSettings[ difficulty ][ "convergencePitchTime" ], "pitch" );	
    self SetConvergenceTime( level._sentryTurretSettings[ difficulty ][ "convergenceYawTime" ], "yaw" );    
	self SetSuppressionTime( level._sentryTurretSettings[ difficulty ][ "suppressionTime" ] );
	self SetAiSpread( level._sentryTurretSettings[ difficulty ][ "aiSpread" ] );
	self SetPlayerSpread( level._sentryTurretSettings[ difficulty ][ "playerSpread" ] );

	self.maxrange = 1100;
	self.bullet_armor = minigun_sentry_bullet_armor_enemy;
}

waitActivateButton( bCheck )
{
	if ( !isSP() )
	{
		self endon( "death" );
		self endon( "disconnect" );
	}

	assert( isdefined( bCheck ) );

	if ( bCheck == true )
	{
		while ( !self attackButtonPressed() && !self useButtonPressed() )
			wait 0.05;
	}
	else if ( bCheck == false )
	{
		while ( self attackButtonPressed() || self useButtonPressed() )
			wait 0.05;
	}
}

makeSentrySolid()
{
	self makeTurretSolid();
}

makeSentryNotSolid()
{
	self.contents = self setContents( 0 );
}


SentryPowerOn()
{
	self setMode( sentry_mode_name_on );
	self.battery_usage = true;
}

SentryPowerOff()
{
	self setMode( sentry_mode_name_off );
	self.battery_usage = false;
}

// =============================================================================
// MP functions:
// =============================================================================


// MP sentry team and head icons
sentry_team_setup( sentry_gun )
{
	// self == player

	assert( isDefined( sentry_gun ) );
	assert( isDefined( sentry_gun.sentryType ) );
	
	if ( isdefined( self.pers[ "team" ] ) )
		sentry_gun.pers[ "team" ] = self.pers[ "team" ];

	sentry_gun sentry_team_show_icon();
}


sentry_team_show_icon()
{
	assert( isdefined( level._func[ "setTeamHeadIcon" ] ) );

	sentry_headicon_offset = ( 0, 0, 65 );
	if ( self.sentryType == "sentry_gun" )
		sentry_headicon_offset = ( 0, 0, 75 );

	self [[ level._func[ "setTeamHeadIcon" ] ]]( self.pers[ "team" ], sentry_headicon_offset );
}


// MP clear team and head icons
sentry_team_hide_icon()
{
	assert( isdefined( level._func[ "setTeamHeadIcon" ] ) );
	self [[ level._func[ "setTeamHeadIcon" ] ]]( "none", (0, 0, 0) );
}


// resets sentry placement mode when owner carrying sentry dies
sentry_place_mode_reset()
{
	if ( !isDefined(self.owner) )
		return;
		
	if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) )
	{
		self.owner notify( "sentry_placement_canceled" );
		self.owner _enableWeapon();
		//self.owner _enableUsability();
		self.owner.placingSentry = undefined;
		self setSentryCarried( false );
		self SetCanDamage( true );
		self.ignoreMe = false;
	}
}

sentry_set_owner( owner )
{
	assert( isdefined( owner ) );
	assert( isPlayer( owner ) );
	
	// don't need to set it twice. will happen for non-static sentries
	if ( isDefined ( self.owner ) && self.owner == owner )
		return;

	owner.debug_sentry			 = self;// for debug
	self.owner 					 = owner;
	self SetSentryOwner( owner );
	self SetTurretMinimapVisible( true );
}

sentry_destroy_on_owner_leave( owner )
{
	level endon( "game_ended" );
	self endon( "death" );

	owner waittill_any( "disconnect", "joined_team", "joined_spectators" );
	self notify( "death" );
}

// battery monitor, batter only used while sentry is on
sentry_die_on_batteryout()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "deleted" );

	// only one instance
	self notify( "battery_count_started" );
	self endon( "battery_count_started" );

	while ( self.sentry_battery_timer >= 0 )
	{
		if ( self.battery_usage )
			self.sentry_battery_timer -= 1;
		wait 1;
	}

	self notify( "death" );
}

removeDeadSentry()
{
	self playsound( "sentry_explode" );
	playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" );
	self delete_sentry_turret();
}


sentry_reset_on_owner_death()
{
	// self is sentry
	assert( isDefined( self ) );
	self endon( "death" );
	self endon( "deleted" );

	assert( isdefined( self.owner ) );
	self.owner waittill_any( "death", "disconnect" );

	if ( isDefined( self.owner.placingSentry ) && (self.owner.placingSentry == self) )
	{
		self.owner.placingSentry = undefined;
		self setSentryCarried( false );
		self SetCanDamage( true );
		self.ignoreMe = false;
		self notify( "death" );
	}
}

sentry_attacker_can_get_xp( sentry )
{
	assert( isdefined( sentry.owner ) );

	// defensive much?
	if ( !isdefined( self ) )
		return false;

	if ( !isPlayer( self ) )
		return false;

	if ( !isdefined( level._onXPEvent ) )
		return false;

	if ( !isdefined( self.pers[ "team" ] ) )
		return false;

	if ( !isdefined( sentry.team ) )
		return false;

	if ( !level._teambased && self == sentry.owner )
		return false;

	if ( level._teambased && ( self.pers[ "team" ] == sentry.team ) )
		return false;

	return true;
}


sentry_attacker_is_friendly( sentry )
{
	assert( isdefined( sentry.owner ) );

	// defensive much?
	if ( !isdefined( self ) )
		return false;

	if ( !isPlayer( self ) )
		return false;

	if ( !level._teamBased )
		return false;
	
	if ( self == sentry.owner )
		return false;

	if ( self.team != sentry.team )
		return false;

	return true;	
}


sentry_emp_damage_wait()
{
	self endon( "deleted" );
	self endon( "death" );

	for ( ;; )
	{
		self waittill( "emp_damage", attacker, duration );

		// TODO: friendly fire check here

		self thread sentry_burst_fire_stop();

		if ( isdefined( level._laserOff_func ) )
			self call [[ level._laserOff_func ]]();

		self SentryPowerOff();
		playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" );

		wait( duration );

		self SentryPowerOn();
	}
}


sentry_emp_wait()
{
	self endon( "deleted" );
	self endon( "death" );

	for ( ;; )
	{
		level waittill( "emp_update" );

		// TODO: make this work in FFA
		if ( level._teamEMPed[self.team] )
		{
			self thread sentry_burst_fire_stop();
	
			if ( isdefined( level._laserOff_func ) )
				self call [[ level._laserOff_func ]]();
	
			self SentryPowerOff();
			playfxOnTag( getfx( "sentry_turret_explode" ), self, "tag_aim" );
		}
		else
		{
			self SentryPowerOn();
		}
	}
}

addToTurretList()
{
	level._turrets[self getEntityNumber()] = self;	
}

removeFromTurretList()
{
	level._turrets[self getEntityNumber()] = undefined;
}

dual_waittill( ent1, msg1, ent2, msg2 )
{
	ent1 endon ( msg1 );
	ent2 endon ( msg2 );
	
	level waittill ( "hell_freezes_over_AND_THEN_thaws_out" );
}