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

global_inits()
{
	maps\_uaz::main( "vehicle_uaz_hardtop", "uaz_physics" );
	maps\_littlebird::main( "vehicle_little_bird_bench" );
	maps\_pavelow::main( "vehicle_pavelow" );
	maps\_littlebird::main( "vehicle_little_bird_armed" );
	
	//maps\createart\estate_art::main();
	maps\createfx\estate_audio::main();
	maps\createart\estate_fog::main();
	
	maps\estate_anim::main();
	maps\estate_fx::main();
	
	maps\_load::main();
	
	level.autosave_proximity_threat_func = ::estate_autosave_proximity_threat_func;
	
	maps\_drone_ai::init();
	
	maps\_slowmo_breach::slowmo_breach_init();
	
	common_scripts\_sentry::main();
	
	level.customIntroAngles = intro_angles();	//Makes the view point to a specific direction at the end of slam zoom
	
	PreCacheItem( "claymore" );
	PreCacheItem( "flash_grenade" );
	PrecacheItem( "javelin" );
	PreCacheItem( "cheytac" );
	PreCacheModel( "projectile_bouncing_betty_grenade" );
	PreCacheModel( "accessories_gas_canister_highrez" );
	PreCacheModel( "prop_cigarette_pack" );
	PreCacheModel( "prop_price_cigar" );
	PreCacheModel( "weapon_colt_anaconda" );
	PreCacheModel( "mil_wireless_dsm_small" );
	PreCacheModel( "electronics_camera_pointandshoot_animated" );
	PreCacheTurret( "minigun_littlebird_spinnup" );
	PreCacheShellShock( "estate_bouncingbetty" );
	
	PreCacheRumble( "artillery_rumble" );
	PreCacheRumble( "shepherd_pistol" );
	PreCacheRumble( "shot_collapse" );
	PreCacheRumble( "bodytoss_impact" );
	PreCacheRumble( "dsm_rummage" );
	
	precachestring( &"ESTATE_DSM_FRAME" );
	precachestring( &"ESTATE_DSM_WORKING" );
	precachestring( &"ESTATE_DSM_NETWORK_FOUND" );
	precachestring( &"ESTATE_DSM_IRONBOX" );
	precachestring( &"ESTATE_DSM_BYPASS" );
	precachestring( &"ESTATE_DSM_PROGRESS" );
	precachestring( &"ESTATE_DSM_SLASH_TOTALFILES" );
	precachestring( &"ESTATE_DSM_DLTIMELEFT" );
	precachestring( &"ESTATE_DSM_DLTIMELEFT_MINS" );
	precachestring( &"ESTATE_DSM_DLTIMELEFT_SECS" );
	precachestring( &"ESTATE_DSM_DL_RATEMETER" );
	precachestring( &"ESTATE_DSM_DLRATE" );
	
	precachestring( &"ESTATE_DSM_DESTROYED_BY_PLAYER" );
	precachestring( &"ESTATE_DSM_DESTROYED_BY_AI_GUNFIRE" );
	precachestring( &"ESTATE_DSM_DESTROYED_BY_DESERTION" );
	
	precachestring( &"ESTATE_LEARN_PRONE" );
	precachestring( &"ESTATE_LEARN_PRONE_TOGGLE" );
	precachestring( &"ESTATE_LEARN_PRONE_HOLDDOWN" );
	
	precachestring( &"ESTATE_USE_CLAYMORE_HINT" );
	
	precachestring( &"ESTATE_REMIND_PRONE_LINE1" );
	precachestring( &"ESTATE_REMIND_PRONE_LINE2" );
	
	precachestring( &"ESTATE_REMIND_PRONE_LINE2_TOGGLE" );
	precachestring( &"ESTATE_REMIND_PRONE_LINE2_HOLDDOWN" );
	
	thread maps\estate_amb::main();
	//maps\_utility::set_vision_set( "estate", 0 );
	
	level.bouncing_betty_clicks = 0;
	level.custom_linkto_slide = true;
	
	level.forestEnemyCount = 0;
	
	level.activeSmokePots = 0;
	level.activeSmokePotLimit = 7;
	level.smokeTimeOut = 15;	//originally 10
	level.activeGameplayMines = 0;
	level.canSave = true;
	
	maps\_compass::setupMiniMap("compass_map_estate");
	
	level.scr_sound[ "mortar" ][ "incomming" ] = "mortar_incoming";
	level.scr_sound[ "mortar" ][ "dirt" ] = "mortar_explosion_dirt";
	level.alwaysquake = 1;
	
	add_hint_string( "claymore_hint", &"ESTATE_USE_CLAYMORE_HINT", ::should_break_claymore_hint );
		
	//PC Prone Key Config Handling - there are three different bindable controls for going prone on PC		
		
	//"toggleprone" (press prone once, stays prone)
	//Msg: "Press^3 [{toggleprone}] ^7to evade the landmine!"
	add_hint_string( "mineavoid_hint_toggle", &"ESTATE_LEARN_PRONE_TOGGLE", ::should_break_mineavoid_hint );
	
	//"+prone" (press and hold to go prone, gets up as soon as key is released)
	//Msg: "Press and hold down^3 [{+prone}] ^7to evade the landmine!"
	add_hint_string( "mineavoid_hint_holddown", &"ESTATE_LEARN_PRONE_HOLDDOWN", ::should_break_mineavoid_hint );
	
	// This last one works for console automatically
	// Press and hold^3 [{+stance}] ^7to evade the landmine!
	add_hint_string( "mineavoid_hint", &"ESTATE_LEARN_PRONE", ::should_break_mineavoid_hint );
	
	level.friendlyForestProgress = 0;
	
	level.perimeter_jeepguards = 0;
	
	level.mainfloorPop = 0;
    level.topfloorPop = 4;	//4 in the top room
    level.basementPop = 5;	//2 in the armory, 3 fleeing from the guestroom
	
	level.interiorRoomsBreached = 0;
	
	level.dsmHealthMax = 3000;	//health
	level.dsmHealth = level.dsmHealthMax;
	level.dsm_regen_dist_limit = 1000; 	//world units
	level.dsm_regen_amount = 0;
	level.dsm_regen_amount_max = 50;	//health per second
	
	level.playerDSMsafedist = 1500;	//player must be within this many units for the game to save during the defense
	
	level.enemyTotalPop = 0;
	level.enemyPop = 0;
	
	level.breachesDone = 3;
	
	level.usedFirstWarning = 0;
	level.usedSecondWarning = 0;
	level.early_escape_timelimit = 30;
	level.dangerZoneTimeLimit = 44;
	
	level.ending_attacker_deaths = 0;
	
	run_thread_on_targetname( "forest_friendly_advance_trig", ::forest_friendly_trigger_erase );
	
	run_thread_on_targetname( "breach_normalguy", ::house_extra_breachguys );
	array_spawn_function_targetname( "breach_normalguy", ::house_extra_breachguys_init );
	
	array_spawn_function_targetname( "breach_extraguy", ::house_floorpop_deathmonitor_init, 1 );
	array_spawn_function_targetname( "breach_extraguy", ::house_extras_stopcancel );
	
	array_spawn_function_targetname( "breach_enemy_spawner", ::house_floorpop_deathmonitor_init );
	array_spawn_function_targetname( "breach_normalguy", ::house_floorpop_deathmonitor_init );
	
	thread bouncing_betty_gameplay_saveguard();
	thread house_guestroom_breach_flee();
	thread house_armory_breach_windowtrick();
	thread house_furniture_sounds();
	
	thread solar_panels();
	
	//===========
	
	thread zone_detection_init();
	thread defense_schedule_control();
	thread defense_enemy_spawn();
	
	thread magic_sniper_guy();
	thread magic_sniper_guy_breaktimes();
	
	thread ending_shadowops_dronespawn();
	
	thread terminal_hunters();
	thread terminal_blockers();
	thread terminal_hillchasers();
	
	//===========
	
	thread abandonment_start_monitor();
	thread abandonment_early_escape_timer();
	thread abandonment_exit_tracking();
	thread abandonment_main_check();
	thread abandonment_failure();
}

forest_friendly_trigger_erase()
{
	self endon ( "death" );
	
	otherTrigs = undefined;
	
	if ( isdefined( self.script_linkto ) )
	{
		otherTrigs = [];
		otherTrigs = self get_linked_ents();
		
		foreach( otherTrig in otherTrigs )
		{
			otherTrig.trig = self;
		}
	}
	
	self waittill ( "trigger" );
	
	assertEX( self.script_index == level.friendlyForestProgress + 1, "goingbackwards" );
	
	level.friendlyForestProgress = self.script_index;
	
	if ( isdefined( otherTrigs ) )
	{
		foreach( otherTrig in otherTrigs )
		{
			otherTrig delete();
		}
	}
	else
	{
		if( isdefined( self.trig ) )
		{
			self.trig delete();
		}
	}
}


should_break_mineavoid_hint()
{
	if( level.player getstance() == "prone" )
	{
		return( true );
	}
	else
	{	
		return( false );
	}
}

flags()
{
	flag_init( "room_1_frontdoor_cleared" );
	flag_init( "room_1_kitchen_cleared" );
	flag_init( "room_2_laundry_cleared" );
	flag_init( "room_3_armory_cleared" );
	flag_init( "room_5_masterbed_cleared" );
	
	flag_init( "slam_zoom_done" );
	
	flag_init( "skip_intro" );
	flag_init( "skip_ambush" );
	flag_init( "skip_forestfight" );
	flag_init( "skip_houseapproach" );
	flag_init( "skip_housebreach" );
	flag_init( "skip_breachandclear" );
	flag_init( "skip_housebriefing" );
	flag_init( "skip_defense" );
	
	flag_init( "test_alt_ending" );
	
	flag_init( "print_first_objective" );
	flag_init( "futilejeeps_destroyed" );
	
	flag_init( "start_ghost_intro_nav" );
	flag_init( "downhill_run_enable" );
	flag_init( "downhill_run_disable" );
	
	flag_init( "ambush_shouted" );
	
	flag_init( "deploy_rpg_ambush" );
	flag_init( "deploy_mortar_attack" );
	
	flag_init( "gameplay_mine_done" );
	
	flag_init( "bouncing_betty_activated" );
	flag_init( "bouncing_betty_done" );
	flag_init( "mine_throw_player" );
	flag_init( "bouncing_betty_player_released" );
	flag_init( "ambushed_player_back_to_normal" );
	flag_init( "slow_motion_ambush_done" );
	flag_init( "spawn_first_ghillies" );
	flag_init( "smoke_screen_activated" );
	flag_init( "smoke_screen_assault_activated" );
	flag_init( "player_exits_smoke" );
	flag_init( "start_ambush_music" );
	
	flag_init( "stop_smokescreens" );
	
	flag_init( "ambush_complete" );
	
	flag_init( "player_is_out_of_ambush_zone" );
	
	flag_init( "forestfight_littlebird_1" );
	flag_init( "start_early_helicopter" );
	
	flag_init( "approaching_house" );
	
	flag_init( "deploy_house_defense_jeeps" );
	
	flag_init( "autosave_housearrival" );
	
	flag_init( "scripted_dialogue_on" );
	
	flag_init( "first_free_save" );
	
	flag_init( "dialogue_ghost_orders" );
	flag_init( "dialogue_topfloor_cleared" );
	flag_init( "dialogue_basement_cleared" );
	
	flag_init( "save_the_game_indoors" );
	flag_init( "save_the_game_downstairs" );
	
	flag_init( "foyer_breached_first" );
	flag_init( "kitchen_breached_first" );
	flag_init( "basement_breached_first" );
	
	flag_init( "breach0_foyerhall_cancel" );
	flag_init( "breach0_diningroom_cancel" );
	flag_init( "breach0_bathroomrush_cancel" );
	
	flag_init( "ghost_begins_sweep" );
	
	flag_init( "ghost_at_bottom_of_stairs" );
	
	flag_init( "ghost_goes_outside" );
	
	flag_init( "topfloor_breached" );
	flag_init( "basement_breached" );
	flag_init( "armory_breached" );
	flag_init( "guestroom_breached" );
	
	flag_init( "furniture_moving_sounds" );
	
	flag_init( "scarecrow_said_upstairs" );
	
	flag_init( "mainfloor_cleared" );
	flag_init( "topfloor_cleared" );
	flag_init( "basement_cleared" );
	
	flag_init( "mainfloor_cleared_confirmed" );
	flag_init( "basement_cleared_confirmed" );
	
	flag_init( "ghost_gives_regroup_order" );
	
	flag_init( "house_friendlies_instructions_given" );
	
	flag_init( "house_exterior_has_been_breached" );
	flag_init( "house_interior_breaches_done" );
	flag_init( "all_enemies_killed_up_to_house_capture" );
	
	flag_init( "house_approach_dialogue" );
	flag_init( "house_perimeter_softened" );
	
	flag_init( "house_briefing_is_over" );
	flag_init( "photographs_done" );
	
	flag_init( "house_briefing_dialogue_done" );
	
	flag_init( "dsm_ready_to_use" );
	flag_init( "download_started" );
	flag_init( "download_files_started" );
	flag_init( "dsm_exposed" );
	flag_init( "dsm_destroyed" );
	flag_init( "download_test" );
	flag_init( "download_data_initialized" );
	
	flag_init( "ozone_to_earlydefense_start" );
	flag_init( "scarecrow_to_earlydefense_start" );
	
	flag_init( "strike_packages_definitely_underway" );
	flag_init( "abandonment_danger_zone" );
	flag_init( "player_can_fail_by_desertion" );
	flag_init( "player_entered_autofail_zone" );
	
	flag_init( "dsm_compromised" );
	
	flag_init( "skip_house_defense_dialogue" );
	
	flag_init( "defense_battle_begins" );
	flag_init( "defending_dsm" );
	flag_init( "sniper_in_position" );	
	
	flag_init( "skip_defense_firstwave" );
	
	flag_init( "sniper_attempting_shot" );	
	flag_init( "sniper_shot_complete" );	
	flag_init( "sniper_shooting" );	
	
	flag_init( "strike_package_birchfield_dialogue" );
	flag_init( "strike_package_bighelidrop_dialogue" );
	flag_init( "strike_package_boathouse_dialogue" );
	flag_init( "strike_package_solarfield_dialogue" );
	flag_init( "strike_package_md500rush_dialogue" );
	flag_init( "rpg_stables_dialogue" );
	flag_init( "rpg_boathouse_dialogue" );
	flag_init( "rpg_southwest_dialogue" );
	flag_init( "scarecrow_death_dialogue" );	
	flag_init( "ozone_death_dialogue" );
	flag_init( "sniper_breaktime_dialogue" );
	
	flag_init( "boathouse_invaders_arrived" );
	
	flag_init( "activate_package_on_standby" );
	flag_init( "strike_package_spawned" );
	flag_init( "strike_component_activated" );
	
	flag_init( "activate_component_on_standby" );
	
	flag_init( "main_defense_fight_finished" );
	
	flag_init( "download_complete" );
	flag_init( "dsm_recovered" );
	
	flag_init( "begin_escape_music" );
	
	flag_init( "fence_removed" );
	
	flag_init( "player_is_escaping" );
	
	flag_init( "cointoss_done" );
	
	flag_init( "birchfield_cleared_sector1" );
	flag_init( "birchfield_cleared_sector2" );
	
	flag_init( "ghost_covered_player" );
	
	flag_init( "bracketing_mortars_started" );
	flag_init( "player_retreated_into_birchfield" );
	
	flag_init( "point_of_no_return" );
	
	flag_init( "finish_line" );
	
	flag_init( "begin_ending_music" );
	
	flag_init( "play_ending_sequence" );
	
	flag_init( "drag_sequence_slacker_check" );
	flag_init( "drag_sequence_killcount_achieved" );
	
	flag_init( "no_slow_mo" );
	
	flag_init( "start_playerdrag_sequence" );
	
	flag_init( "test_with_pavelow_already_in_place" );
	
	flag_init( "thunderone_heli" );
	
	flag_init( "enter_the_littlebirds" );
	
	flag_init( "made_it_to_lz" );
	
	flag_init( "test_whole_ending" );
	flag_init( "test_ending" );
	flag_init( "ghost_grabbed_player" );
	flag_init( "test_ending_body_toss" );
	flag_init( "begin_overlapped_gasoline_sequence" );
	flag_init( "cigar_flicked" );
	flag_init( "cigar_flareup" );
	
	flag_init( "end_the_mission" );
	
	flag_init( "claymore_hint_printed" );
}

intro_angles()
{
	startPoint = getent( "player_view_start_1", "targetname" );
	endPoint = getent( "player_view_start_2", "targetname" );
	
	vec = endPoint.origin-startPoint.origin;
	ang = vectorToAngles( vec );
	return( ang );
}

ghost_init()
{
	// spawn ghost
	level.ghost = spawn_targetname( "ghost", true );
	level.ghost.animname = "ghost";
	
	if( !flag( "test_ending" ) )
	{
		level.ghost thread downhill_run();
		level.ghost.pathRandomPercent = 0;	
		level.ghost thread magic_bullet_shield();
		
		if( !flag( "skip_houseapproach" ) )
		{
			level.ghost allowedstances ( "crouch" );
		}
		
		level.ghost pushplayer( true );
	
		level.ghost enable_cqbwalk();
		level.ghost.ignoresuppression = true;
	}
}

downhill_run()
{
	while( 1 )
	{
		guy = trigger_wait_targetname( "downhill_run_enable" );
		
		if( guy == self )
		{
			break;
		}
	}

	self disable_cqbwalk();
	self set_run_anim( "downhill_run" );
	
	while( 1 )
	{
		guy = trigger_wait_targetname( "downhill_run_disable" );
		
		if( guy == self )
		{
			break;
		}
	}
	
	if( self == level.ghost )
	{
		self allowedstances ( "stand", "crouch", "prone" );
	}
	
	self enable_cqbwalk();
	self clear_run_anim();
}

ghost_intro_nav()
{	
	//If player waits for intro sequence to end, Ghost gets up and moves out
	
	level endon ( "ghost_leaving_start_area_early" );
	
	flag_wait( "start_ghost_intro_nav" );
	
	level notify ( "ghost_leaving_start_area_on_cue" );
	
	starter = getent( "ghost_starter", "targetname" );
	
	if( isdefined( starter ) )
		starter notify ( "trigger" );

	level.ghost allowedstances ( "crouch", "prone", "stand" );
}

ghost_intro_interrupt()
{
	//If player rushes away from start location, Ghost gets up and moves out right away
	
	level endon ( "ghost_leaving_start_area_on_cue" );
	level notify ( "ghost_leaving_start_area_early" );
	
	starter = getent( "ghost_starter", "targetname" );
	
	if( isdefined( starter ) )
		starter waittill ( "trigger" );

	level.ghost allowedstances ( "crouch", "prone", "stand" );
}

friendly_troop_spawn()
{
    array_spawn_function_targetname( "starter_friendly", ::friendly_troop_init );
    starter_friendly_spawns = getentarray( "starter_friendly", "targetname" );
    array_thread( starter_friendly_spawns, ::spawn_ai );
}

friendly_troop_init()
{
    //self.disableArrivals = true;
    self.ignoresuppression = true;
    self pushplayer( true );
    self enable_cqbwalk();
    
    badass = isdefined( self.script_friendname );
    
    if ( badass )
    {
    	self thread magic_bullet_shield();
    	
    	if ( self.script_friendname == "Ozone" )
    	{
    		level.ozone = self;
    		level.ozone.animname = "ozone";
    		level.ozone thread downhill_run();
			level.ozone thread deathtalk_ozone();
			level.ozone thread ozone_death_decide();
    	}
    	
    	if ( self.script_friendname == "Scarecrow" )
    	{
    		level.scarecrow = self;
    		level.scarecrow.animname = "scarecrow";
    		level.scarecrow thread downhill_run();
    		level.scarecrow thread deathtalk_scarecrow();
    		level.scarecrow thread scarecrow_death_decide();
    	}
    } 
}

friendly_scout_spawn()
{
	array_spawn_function_targetname( "starter_scout", ::friendly_scout_init );
    starter_scout_spawns = getentarray( "starter_scout", "targetname" );
    array_thread( starter_scout_spawns, ::spawn_ai );   
}

friendly_scout_init()
{
    //self.disableArrivals = true;
    self.ignoresuppression = true;
    self pushplayer( true );
    self enable_cqbwalk();
    
    self thread friendly_die_in_ambush();
}

friendly_die_in_ambush()
{
	self endon ( "death" );
	
	flag_wait( "mine_throw_player" );	
	
	wait randomfloatrange( 0.1, 1 );
	
	self kill();
}

friendly_sniper_spawn()
{
    array_spawn_function_targetname( "starter_sniper", ::friendly_sniper_init );
    starter_sniper_spawns = getentarray( "starter_sniper", "targetname" );
    array_thread( starter_sniper_spawns, ::spawn_ai );  
}

friendly_sniper_init()
{
	self endon ( "death" );
	
    //self.disableArrivals = true;
    self.ignoresuppression = true;
    self pushplayer( true );
    self allowedStances( "crouch" );
    
    flag_wait( "player_is_out_of_ambush_zone" );
    self delete();
}

redshirtStartSpawnTeleport( startPointName )
{
	friendly_troop_spawn();
	
	allies = getaiarray( "allies" );
	
	foreach( ally in allies )
	{
		if( isdefined( ally.script_friendname ) )
		{
			if ( ally.script_friendname == "Ozone" )
			{
				node = getnode( "ozone_" + startPointName + "_start", "targetname" );
				ally forceTeleport( node.origin, node.angles );
			}
			
			if ( ally.script_friendname == "Scarecrow" )
			{
				node = getnode( "scarecrow_" + startPointName + "_start", "targetname" );
				ally forceTeleport( node.origin, node.angles );
			}
		}
		else
		{
			ally delete();
		}
	}
}

house_perimeter_jeepguards()
{
	array_spawn_function_noteworthy( "perimeter_guard", ::house_perimeter_jeepguards_init );
}

house_perimeter_jeepguards_init()
{
	level.perimeter_jeepguards++;
	
	self thread house_perimeter_jeepguards_deathmonitor();
}

house_perimeter_jeepguards_deathmonitor()
{
	self waittill ( "death" );
	
	level.perimeter_jeepguards--;
	
	if( !level.perimeter_jeepguards )
	{
		flag_set( "house_perimeter_softened" );
	}
}

/*==========================================

BOUNCING BETTY SEQUENCE

==========================================*/

bouncing_betty_slow_mo()
{	
	thread bouncing_betty_activate();
	
	// don't slomo the mission critical speech
	SoundSetTimeScaleFactor( "Mission", 0 );
	SoundSetTimeScaleFactor( "Shellshock", 0 );
	SoundSetTimeScaleFactor( "Voice", 0 );
	SoundSetTimeScaleFactor( "Menu", 0 );
	SoundSetTimeScaleFactor( "Effects1", 0.8 );
	SoundSetTimeScaleFactor( "Effects2", 0.8 );
	SoundSetTimeScaleFactor( "Announcer", 0 );
	
	slomoLerpTime_in = 2;		//1.5
	slomoLerpTime_out = 0.65;	//0.65
	slomobreachplayerspeed = 0.1;	//0.1
	slomoSpeed = 0.3;	//0.25
	slomoDuration = 10;	//24
	
	//level.player thread play_sound_on_entity( "mine_betty_spin" );
	
	music_stop();
	
	slowmo_start();
	slowmo_setspeed_slow( slomoSpeed );
	slowmo_setlerptime_in( slomoLerpTime_in );
	slowmo_lerp_in();
	
	flag_set( "deploy_rpg_ambush" );
	
	level.player SetMoveSpeedScale( slomobreachplayerspeed );
	
	wait slomoDuration * slomoSpeed;
	
	level.player thread play_sound_on_entity( "mine_betty_spin_slomo" );
	
	slowmo_setlerptime_out( slomoLerpTime_out );
	slowmo_lerp_out();
	slowmo_end();
	level.player SetMoveSpeedScale( 1.0 );
	
	
	flag_set( "slow_motion_ambush_done" );
}

playerFovGroundSpot( specialPlayerCase )
{
	angles = level.player getplayerangles();
	
	angles = ( 0, angles[ 1 ], 0 );	//only use yaw component
	
	forward = anglestoforward( angles );
	right = anglestoright( angles );
	left = right * ( -1 );
	
	mineOrg = level.player.origin + forward * 96; //96 units ahead of player
	
	if( isdefined( specialPlayerCase ) )
	{
		startPoint = level.player.origin + ( 0, 0, 64 );
		
		//head top traces
		
		endPointForward = level.player.origin + forward * 128 + ( 0, 0, 64 );
		//Print3d( endPointForward, "x1", ( 0, 0, 1 ), 1, 1, 15000 );
		
		endPointRight = endPointForward + right * 16;
		//Print3d( endPointRight, "r1", ( 0, 0, 1 ), 1, 1, 15000 );
		
		endPointLeft = endPointForward + left * 16;
		//Print3d( endPointLeft, "l1", ( 0, 0, 1 ), 1, 1, 15000 );
		
		//waist high traces
		
		endPointMidForward = level.player.origin + forward * 128 + ( 0, 0, 28 );
		//Print3d( endPointMidForward, "x2", ( 1, 1, 0 ), 1, 1, 15000 );
		
		endPointMidRight = endPointMidForward + right * 16;
		//Print3d( endPointMidRight, "r2", ( 1, 1, 1 ), 1, 1, 15000 );
		
		endPointMidLeft = endPointMidForward + left * 16;
		//Print3d( endPointMidLeft, "l2", ( 1, 1, 1 ), 1, 1, 15000 );
		
		//low blocker traces
		
		//endPointLowForward = level.player.origin + forward * 128 + ( 0, 0, 2 );
		//Print3d( endPointLowForward, "x3", ( 1, 0, 1 ), 1, 1, 15000 );
		
		//endPointLowRight = endPointLowForward + right * 16;
		//Print3d( endPointLowRight, "r3", ( 1, 0, 1 ), 1, 1, 15000 );
		
		//endPointLowLeft = endPointLowForward + left * 16;
		//Print3d( endPointLowLeft, "l3", ( 1, 0, 1 ), 1, 1, 15000 );
		
		test1 = BulletTracePassed( startPoint, endPointForward, true, level.player );
		test2 = BulletTracePassed( startPoint, endPointRight, true, level.player );
		test3 = BulletTracePassed( startPoint, endPointLeft, true, level.player );
		
		test4 = BulletTracePassed( startPoint, endPointMidForward, true, level.player );
		test5 = BulletTracePassed( startPoint, endPointMidRight, true, level.player );
		test6 = BulletTracePassed( startPoint, endPointMidLeft, true, level.player );
		
		//test7 = BulletTracePassed( startPoint, endPointLowForward, true, level.player );
		//test8 = BulletTracePassed( startPoint, endPointLowRight, true, level.player );
		//test9 = BulletTracePassed( startPoint, endPointLowLeft, true, level.player );
		
		if( test1 && test2 && test3 && test4 && test5 && test6 )
		{
			mineOrg = drop_to_ground( mineOrg, 200, -200 );
			return( mineOrg );
		}
		else
		{
			alts = getentarray( "alternate_bb_location", "targetname" );
			
			dist = 128;
			useableLoc = false;
			validLocFound = false;
			
			foreach( alt in alts )
			{
				newdist = length( level.player.origin - alt.origin );
				
				eyePos = level.player GetEye();
				//success = SightTracePassed( eyePos, alt.origin + ( 0, 0, 6 ), true, level.player );
				success = player_can_see_origin( alt.origin + ( 0, 0, 6 ) );
				
				if( success )
				{
					useableLoc = true;
				}
				else
				{
					useableLoc = false;
				}
				
				if( newdist < dist && newdist > 48 && useableLoc )
				{
					validLocFound = true;
					mineOrg = drop_to_ground( alt.origin, 200, -200 );			
					return( mineOrg );
				}
			}
			
			if( !validLocFound )
			{
				newdist = undefined;
				olddist = 100000;
				bestOrg = undefined;
				
				foreach( alt in alts )
				{
					newdist = length( level.player.origin - alt.origin );
					
					if( newdist < olddist )
					{
						olddist = newdist;
						bestOrg = alt;
					}
				}
				
				mineOrg = drop_to_ground( bestOrg.origin, 200, -200 );
				return( mineOrg );
			}
		}
	}
	else
	{
		mineOrg = drop_to_ground( mineOrg, 200, -200 );
		return( mineOrg );
	}
}

bouncing_betty_activate()
{
	wait 0.2;
	
	mines = getentarray( "bouncing_betty", "targetname" );
	
	foreach ( mine in mines )
	{
		mine thread bouncing_betty_fx( undefined, undefined, undefined );
		wait randomfloatrange( 0.15, 0.2 );
	}
	
	//launch player's special personal mine
	
	mineOrg = playerFovGroundSpot( "specialPlayerCase" );
	
	if( isdefined( mineOrg ) )
	{
		thread bouncing_betty_throwplayer( mineOrg );
		
		//playerMine = spawn( "script_origin", level.player.origin + ( 32, -72, 0 ) );
		playerMine = spawn( "script_origin", mineOrg );
		playerMine thread bouncing_betty_fx( 1, undefined );
	}
	else
	{
		thread bouncing_betty_throwplayer( level.player.origin );
		
		playerMine = spawn( "script_origin", level.player.origin );
		playerMine thread bouncing_betty_fx( 1, undefined, 1 );
	}
	
	thread bouncing_betty_hintprint();
	
	while( level.bouncing_betty_clicks < mines.size )
	{
		wait 0.05;
	}
	
	level.player allowSprint( false );
	
	flag_set( "ambush_shouted" );
	
	wait 0.35;
	
	//"AMBUUUSH!!!"
	level.ghost dialogue_queue( "est_gst_ambush" );
	
	wait 1;
	
	flag_set( "bouncing_betty_done" );
}

bouncing_betty_hintprint()
{
	if( level.console )
	{
		wait 0.6;
	}
	else
	{
		wait 0.1;
		
		//PC Prone Key Config Handling - there are three different bindable controls for going prone on PC
		
		if( is_command_bound( "toggleprone" ) )
		{
			//"toggleprone" (press prone once, stays prone)
			//Msg: "Press^3 [{toggleprone}] ^7to evade the landmine!"
			level.player thread display_hint( "mineavoid_hint_toggle" );
		}
		else 
		if( is_command_bound( "+prone" ) )
		{
			//"+prone" (press and hold to go prone, gets up as soon as key is released)
			//Msg: "Press and hold down^3 [{+prone}] ^7to evade the landmine!"
			level.player thread display_hint( "mineavoid_hint_holddown" );
		}
		else
		if( is_command_bound( "+stance" ) )
		{
			//"+stance" (press and hold to go prone)
			//Msg: "Press and hold^3 [{+stance}] ^7to evade the landmine!"
			level.player thread display_hint( "mineavoid_hint" );
		}
	}
}

bouncing_betty_fx( specialPlayerCase, gameplayOn, skipFX )
{
	playFX( getfx( "bouncing_betty_launch" ), self.origin );
	self playsound( "mine_betty_click" );
	self playsound( "mine_betty_spin" );
	
	gameplay = isdefined( gameplayOn );
	skipFX = isdefined( skipFX );
	
	if( !gameplay )
	{
		level.bouncing_betty_clicks++;
	}
	else
	{
		level.canSave = false;
		level.activeGameplayMines++;
	}
	
	spinner = undefined;
	explosion_org = undefined;
	
	if( !skipFX )
	{
		spinner = spawn( "script_model", self.origin );
		spinner setmodel( "projectile_bouncing_betty_grenade" );
		spinner.animname = "bouncingbetty";
		spinner useAnimTree( level.scr_animtree[ "bouncingbetty" ] );
		
		spinner thread play_spinner_fx();
		
		self anim_single_solo( spinner, "bouncing_betty_detonate" );
		
		explosion_org = spinner.origin;
		
		if( !gameplay )
		{
			flag_wait( "ambush_shouted" );
		}
		
		spinner playsound( "grenade_explode_metal" );
	}
	
	if( isdefined( specialPlayerCase ) )
	{	
		if( !skipFX )
		{
			playFXontag( getfx( "bouncing_betty_explosion" ), spinner, "tag_fx" );
		}
		else
		{
			wait 2;
		}
		
		friendlies = getaiarray( "allies" );
		foreach( guy in friendlies )
		{
			if( !isdefined( guy.name ) )
				continue;
				
			if( guy.name != "Archer" && guy.name != "Toad" )
			{
				//knockdown the guy
				guy delaythread( randomfloat( 0.15 ), ::anim_generic, guy, "exposed_crouch_extendedpainA" );
			}
		}
	
		level.player PlayRumbleOnEntity( "artillery_rumble" );
		
		setplayerignoreradiusdamage( false );
			
		if( level.player getstance() == "prone" )
		{
			flag_set( "mine_throw_player" );
		}
		else
		{
			wait 0.05;
			level.player kill();
		}
	}
	else
	{	
		playFXontag( getfx( "bouncing_betty_explosion" ), spinner, "tag_fx" );
		
		spinner hide();
		
		if( gameplay )
		{
			dist = length( level.player.origin - self.origin );

			level.player PlayRumbleOnEntity( "artillery_rumble" );
			
			if( dist <= self.radius )
			{
				if( level.player getstance() == "prone" )
				{
					SetPlayerIgnoreRadiusDamage( true );
				
					//thread bouncing_betty_gameplay_fake_damage();
					thread bouncing_betty_gameplay_real_damage( explosion_org );
				
					rightFovCheck = within_fov( level.player.origin, level.player.angles + (0 , -95, 0 ), self.origin, cos( 180 ) );
					leftFovCheck = within_fov( level.player.origin, level.player.angles + (0 , 95, 0 ), self.origin, cos( 180 ) );
					centerFovCheck = within_fov( level.player.origin, level.player.angles, self.origin, cos( 10 ) );
					
					if( rightFovCheck )
					{
						level.player thread maps\_gameskill::grenade_dirt_on_screen( "right" );	
					}
					
					if( leftFovCheck )
					{
						level.player thread maps\_gameskill::grenade_dirt_on_screen( "left" );
					}
					
					if( centerFovCheck )
					{
						level.player thread maps\_gameskill::grenade_dirt_on_screen( "bottom_b" );
					}
				
					thread bouncing_betty_gameplay_shock();
				}
				else
				{
					wait 0.2;
					
					if( isalive( level.player ) )
					{
						thread bouncing_betty_gameplay_real_damage( explosion_org );
						
						level notify( "new_quote_string" );
						
						level.player kill();
						
						thread bouncing_betty_deathquote();
					}
				}
			}
		}
		else
		{
			SetPlayerIgnoreRadiusDamage( true );
			radiusdamage( explosion_org, self.radius, 1000, 20 );
		}
		
		SetPlayerIgnoreRadiusDamage( false );
	}
	
	wait 0.2;
	
	if( isdefined( spinner ) )
		spinner delete();
	
	wait 0.5;
	
	if( gameplay )
	{
		level.activeGameplayMines--;
		level notify ( "gameplay_mine_done" );
	}
}

bb_autosave()
{
	trig = getent( "forestfight_start_redshirts", "targetname" );
	trig waittill ( "trigger" );
	
	autosave_by_name( "bb_autosave" );
}

bouncing_betty_deathquote()
{
	setDvar( "ui_deadquote", "" );
	
	textOverlay = level.player maps\_hud_util::createClientFontString( "default", 1.75 );
	textOverlay.color = ( 1, 1, 1 );
	
	textOverlay setText( &"ESTATE_REMIND_PRONE_LINE1" );	
	
	textOverlay.x = 0;
	textOverlay.y = -30;
	textOverlay.alignX = "center";
	textOverlay.alignY = "middle";
	textOverlay.horzAlign = "center";
	textOverlay.vertAlign = "middle";
	textOverlay.foreground = true;
	textOverlay.alpha = 0;
	textOverlay fadeOverTime( 1 );
	textOverlay.alpha = 1;
	
	textOverlay2 = level.player maps\_hud_util::createClientFontString( "default", 1.75 );
	textOverlay2.color = ( 1, 1, 1 );
	
	if( level.console )
	{
		textOverlay2 setText( &"ESTATE_REMIND_PRONE_LINE2" );
	}
	else
	{
		//PC Prone Control Handling
		
		if( is_command_bound( "toggleprone" ) )
		{
			//"Press ^3[{toggleprone}]^7 to evade them."
			textOverlay2 setText( &"ESTATE_REMIND_PRONE_LINE2_TOGGLE" );
		}
		else
		if( is_command_bound( "+prone" ) )
		{
			//"Press and hold down ^3[{+prone}]^7 to evade them."
			textOverlay2 setText( &"ESTATE_REMIND_PRONE_LINE2_HOLDDOWN" );
		}
		else
		if( is_command_bound( "+stance" ) )
		{
			textOverlay2 setText( &"ESTATE_REMIND_PRONE_LINE2" );
		}     
	}
	
	textOverlay2.x = 0;
	textOverlay2.y = -5;
	textOverlay2.alignX = "center";
	textOverlay2.alignY = "middle";
	textOverlay2.horzAlign = "center";
	textOverlay2.vertAlign = "middle";
	textOverlay2.foreground = true;
	textOverlay2.alpha = 0;
	textOverlay2 fadeOverTime( 1 );
	textOverlay2.alpha = 1;
}

bouncing_betty_gameplay_shock()
{
	level.player shellshock( "estate_bouncingbetty", 3.5 );
	earthquake( 1, 0.8, level.player.origin, 2000 );
	
	level.player.ignoreme = true;
	wait 3.5;
	level.player.ignoreme = false;
}

bouncing_betty_gameplay_fake_damage()
{
	level.player dodamage( randomfloatrange( 30, 35 ), self.origin );
}

bouncing_betty_gameplay_real_damage( mineOrg )
{
	radiusdamage( mineOrg, self.radius * 2, 200, 20 );
}

bouncing_betty_gameplay_saveguard()
{
	while( 1 )
	{	
		level waittill ( "gameplay_mine_done" );
		
		if( !level.activeGameplayMines )
		{
			level.canSave = true;
		}
	}
}

bouncing_betty_throwplayer( mineOrg )
{
	if( !flag( "ambush_complete" ) )
	{
		flag_wait( "mine_throw_player" );	
	}
	
	level.player freezeControls( true );
		
	vec = ( level.player.origin + ( 0, 0, 40) ) - mineOrg;	//vector from mine to player's upper body
	baseThrust = vectornormalize( vec );
	boost = 2000;	//1800
	
	thread bouncing_betty_shellshock();
	thread bouncing_betty_stances();
	thread ambush_fake_rpg_barrage();
	
	touchingVol = false;
	
	antiSliderVol = getent( "no_sliding_allowed", "targetname" );
	if( !( level.player istouching( antiSliderVol ) ) )
	{
		touchingVol = true;
		level.player thread BeginSliding( baseThrust * boost, 10, 0.25 );
	}
	
	wait 0.5;
	
	if( touchingVol )
		level.player thread endsliding();
	
	flag_set( "bouncing_betty_player_released" );
	
	if( touchingVol )
		wait 3.5;
	
	level.player freezeControls( false );
	
	flag_set( "spawn_first_ghillies" );
}

bouncing_betty_shellshock()
{
	level.player shellshock( "estate_bouncingbetty", 10 );
	
	wait 0.1;
	
	earthquake( 3, 0.2, level.player.origin, 2000 );
	
	wait 0.2;
	
	earthquake( 1, 1, level.player.origin, 2000 );
	wait 1;
	
	//Taargets, left side, left side!!
	level.ghost thread dialogue_queue( "est_gst_targetsleftside" );
	
	earthquake( 0.5, 0.5, level.player.origin, 2000 );
	wait 0.5;
	
	flag_wait( "ambushed_player_back_to_normal" );
	
	level.player AllowCrouch( true );
	level.player AllowStand( true );
	
	level.player allowSprint( true );

	wait 3;
	
	//"AMBUUUSH!!!"
	level.ozone dialogue_queue( "est_tf1_ambush" );
	
	flag_set( "start_ambush_music" );
	
	wait 2;
	
	//"AMBUUUSH!!!"
	level.scarecrow dialogue_queue( "est_tf2_ambush" );
	
	wait 1;
	
	flag_set( "deploy_mortar_attack" );
}

bouncing_betty_stances()
{
	flag_wait( "bouncing_betty_player_released" );
	
	level.player AllowCrouch( false );
	level.player AllowStand( false );
	level.player AllowProne( true );
	
	wait 5;
	
	flag_set( "ambushed_player_back_to_normal" );
}

play_spinner_fx()
{
	self endon( "death");
	timer = gettime() + 1000;
	while ( gettime() < timer )
	{
		wait .05;
		playFXontag( getfx( "bouncing_betty_swirl" ), self, "tag_fx_spin1" );
		playFXontag( getfx( "bouncing_betty_swirl" ), self, "tag_fx_spin3" );
		wait .05;
		playFXontag( getfx( "bouncing_betty_swirl" ), self, "tag_fx_spin2" );
		playFXontag( getfx( "bouncing_betty_swirl" ), self, "tag_fx_spin4" );
	}
}

ambush_fake_rpg_barrage()
{
	fakeRpgs = getstructarray( "fake_rpg", "targetname" );
	
	foreach( index, fakeRpg in fakeRpgs )
	{
		repulsor = Missile_CreateRepulsorEnt( level.player, 1000, 512 );
		fakeRpgTarget = getstruct( fakeRpg.target, "targetname" );
		
		MagicBullet( "rpg", fakeRpg.origin, fakeRpgTarget.origin );
		
		wait randomfloatrange( 0.8, 1.4 );
	}
}

/*==========================================

BOUNCING BETTY GAMEPLAY EDITION

==========================================*/

bouncing_betty_gameplay_init()
{
	mines = getentarray( "bouncing_betty_gameplay", "targetname" );
	
	foreach( mine in mines )
	{
		mine thread bouncing_betty_gameplay();
	}
}

bouncing_betty_gameplay()
{
	level endon ( "house_exterior_has_been_breached" );
	
	mine = spawn( "script_origin", self.origin );
	mine.radius = self.radius;
	mine.origin = self.origin;
	
	detonated = false;
	
	requiredStareTime = 0.15;
	fov_angle = 5;
	
	while( !detonated )
	{
		self waittill ( "trigger" );
		
		fovCheckTime = 0;
	
		fovCheck = within_fov( level.player.origin, level.player.angles, mine.origin, cos( fov_angle ) );
	
		while( fovCheck )
		{
			if( fovCheckTime >= requiredStareTime )
			{
				mine thread bouncing_betty_fx( undefined, 1 );
				detonated = true;
				level notify ( "gameplay_mine_deployed" );
				break;
			}

			fovCheck = within_fov( level.player.origin, level.player.angles, mine.origin, cos( fov_angle ) );
			fovCheckTime = fovCheckTime + 0.05;
			wait 0.05;
		}
	}
}

/*==========================================

FOREST SMOKE SCREENS

==========================================*/

forest_smokepot1()
{
	level endon ( "approaching_house" );
	level endon ( "stop_smokescreens" );
	
	while( 1 )
	{
		flag_wait( "smokepot1" );
		
		flag_clear( "smokepot2" );
		flag_clear( "smokepot3" );
		
		smoke_pots = getentarray( "smokepot1", "targetname" );
		smoke_pots = array_randomize ( smoke_pots );
		
		forest_smokeloop( "smokepot1", smoke_pots );
	}
}

forest_smokepot2()
{
	level endon ( "approaching_house" );
	level endon ( "stop_smokescreens" );
	
	while( 1 )
	{
		flag_wait( "smokepot2" );
		
		flag_clear( "smokepot1" );
		flag_clear( "smokepot3" );
		
		smoke_pots = getentarray( "smokepot2", "targetname" );
		smoke_pots = array_randomize ( smoke_pots );
		
		forest_smokeloop( "smokepot2", smoke_pots );
	}
}

forest_smokepot3()
{
	level endon ( "approaching_house" );
	level endon ( "stop_smokescreens" );
	
	while( 1 )
	{
		flag_wait( "smokepot3" );
		
		flag_clear( "smokepot1" );
		flag_clear( "smokepot2" );
		
		smoke_pots = getentarray( "smokepot3", "targetname" );
		smoke_pots = array_randomize ( smoke_pots );
		
		forest_smokeloop( "smokepot3", smoke_pots );
	}
}

forest_smokeloop( smokeName, smoke_pots )
{
	level endon ( "approaching_house" );
	level endon ( "stop_smokescreens" );
	
	while( flag( smokeName) )
	{
		foreach ( smoke_pot in smoke_pots )
		{
			wait 6;	//originally 5
			
			if( level.activeSmokePots <= level.activeSmokePotLimit )				
			{
				smoke_pot thread smokepot();
				level.activeSmokePots++;
				thread forest_smokepot_timer();
			}	
		}
	
		wait 18;	//originally 15
	}	
}

forest_smokepot_timer()
{
	wait level.smokeTimeOut;
	
	level.activeSmokePots--;
}

/*==========================================

SPAWNING AND NAV

==========================================*/

ghillie_spawn( ghillie_name )
{
	spawners = getentarray( ghillie_name, "targetname" );
	
	assertEX( isdefined( ghillie_name), "ghillie_spawn called without a ghillie_name" );
	
	if ( ghillie_name == "early_sniper" )
	{
		array_spawn_function_targetname( ghillie_name, ::forest_fight_ai_cleanup );
	}
	
	array_spawn_function_targetname( ghillie_name, ::ghillie_init );
	array_thread( spawners, ::spawn_ai );
}

ghillie_init()
{
	self start_in_prone_and_standup();
}

prowler_spawn()
{	
    array_spawn_function_targetname( "prowler1", ::prowler_init_1 );
    array_spawn_function_targetname( "prowler1", ::forest_fight_ai_cleanup );
    array_spawn_function_targetname( "prowler2", ::prowler_init_2 );
    array_spawn_function_targetname( "prowler2", ::forest_fight_ai_cleanup );
    array_spawn_function_targetname( "prowler3", ::prowler_init_3 );
    array_spawn_function_targetname( "prowler3", ::forest_fight_ai_cleanup );
    
    spawners = [];
    spawners = getentarray( "prowler1", "targetname" );
    array_thread( spawners, ::spawn_ai );  
    
    spawners = [];
    spawners = getentarray( "prowler2", "targetname" );
    array_thread( spawners, ::spawn_ai );  
    
    spawners = [];
    spawners = getentarray( "prowler3", "targetname" );
    array_thread( spawners, ::spawn_ai );  
}

forest_fight_ai_cleanup()
{
	self endon ( "death" );
	
	flag_wait( "forestfight_littlebird_1" );
	
	wait randomfloatrange( 1.5, 4.7 );
	
	self.goalradius = 2000;
	self.forcegoal = 1;
	
	testnumber = randomint( 100 );
	
	if( testnumber >= 75 )
	{
		while( 1 )
		{
			self setGoalPos( level.player.origin );
			wait randomfloatrange( 5, 10 );
		}
	}
	else
	{
		node = getnode( "forestfight_cleanup_enemy_rallypoint", "targetname" );
		self setgoalnode( node );
	}
}

prowler_init_1()
{
	self thread forest_enemy_deathmonitor();
	self thread forest_enemy_groundlevel_magicsnipe_cleanup();

	self start_in_prone_and_standup();
		
    //self.disableArrivals = true;
    self.ignoresuppression = true;
    self pushplayer( true );
    
   	node = getnode( "prowler1_start", "targetname" );
    	
    nodeLoiterTime = 5;
    nodeInitRadius = 4420;
    nodeEndRadius = 2400;
    nodeClosureRate = 0.1;
    nodeClosureInterval = 5;
    earlyEndonMsg = "forestfight_littlebird_1";
    	
    self thread roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
}

prowler_init_2()
{
	self thread forest_enemy_deathmonitor();
	self thread forest_enemy_groundlevel_magicsnipe_cleanup();
   
   	self start_in_prone_and_standup();
   
   	node = getnode( "prowler2_start", "targetname" );
	
	nodeLoiterTime = 4;
	nodeInitRadius = 3700;
	nodeEndRadius = 2200;
	nodeClosureRate = 0.1;
	nodeClosureInterval = 5;
	earlyEndonMsg = "forestfight_littlebird_1";
    	
   	self thread roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
}
    
prowler_init_3()
{
  	node = getnode( "prowler3_start", "targetname" );
  	
  	self thread forest_enemy_deathmonitor();
  	self thread forest_enemy_groundlevel_magicsnipe_cleanup();
    	
	nodeLoiterTime = 3;
	nodeInitRadius = 4500;
	nodeEndRadius = 3400;
	nodeClosureRate = 0.08;
	nodeClosureInterval = 4;
	earlyEndonMsg = "forestfight_littlebird_1";
    	
   	self thread roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
}

start_in_prone_and_standup()
{
	self endon ( "death" );
	self endon ( "long_death" );
	
	self.animname = "ghillie";
	self.allowdeath = 1;
	self.a.pose = "prone";
		
	wait randomfloatrange( 0, 0.2 );
	
	self thread anim_single_solo( self , "prone_2_stand_firing" );
}

forest_enemy_deathmonitor()
{
	level.forestEnemyCount++;
	
	self waittill ( "death" );
	
	level.forestEnemyCount--;
	
	if( level.forestEnemyCount < 3 )
	{
		flag_set( "stop_smokescreens" );
	}
}

forest_enemy_groundlevel_magicsnipe_cleanup()
{
	self endon ( "death" );
	
	flag_wait_any( "approaching_house", "stop_smokescreens" );
	
	wait randomfloatrange( 1.5, 2.25 );
	
	ent = getent( "futilejeep_javelin_sourcepoint1", "targetname" );
	
	self kill();
}

/******************************

SLOW-MOTION BREACHING

******************************/

house_guestroom_breach_flee()
{
	//Guys in the guestroom try to escape
	
	openDoors = getent( "recroom_open_doors", "targetname" );
	openDoors hide();
	
	level waittill ( "breaching_number_" + "4" );
	
	closedDoors = getent( "recroom_closed_doors", "targetname" );
	closedDoors delete();
	
	openDoors show();
	
	level.forced_slowmo_breach_slowdown = true;
	
	wait 5;
	
	level.forced_slowmo_breach_slowdown = undefined;
}

house_guestroom_door_remove()
{
	//for any start point after the house breaches are done
	
	openDoors = getent( "recroom_open_doors", "targetname" );
	openDoors hide();

	closedDoors = getent( "recroom_closed_doors", "targetname" );
	closedDoors delete();

	openDoors show();
}

house_armory_breach_windowtrick()
{
	window_shards = getentarray( "window_brokenglass", "targetname" );
	foreach( shard in window_shards )
	{
		shard hide();
	}
	
	level waittill ( "breaching_number_3" );
	
	wait 2;
	
	window_sightblocker = getent( "paper_window_sightblocker", "targetname" );
	window_sightblocker delete();
	
	window_newspapers = getentarray( "window_newspaper", "targetname" );
	foreach( window_newspaper in window_newspapers )
	{
		window_newspaper delete();
	}
	
	window_panes = getentarray( "window_pane", "targetname" );
	foreach( window_pane in window_panes )
	{
		window_pane delete();
	}
	
	foreach( shard in window_shards )
	{
		shard show();
	}
	
	blinds = getentarray( "window_blinds", "targetname" );
	foreach( blind in blinds )
	{
		blind delete();
	}
	
	smashers = getentarray( "window_smasher", "targetname" );
	foreach( smasher in smashers )
	{
		radiusdamage( smasher.origin, smasher.radius, 1000, 1000 );
	}
}

house_furniture_sounds()
{
	flag_wait( "furniture_moving_sounds" );
	
	level endon ( "breaching_number_" + "5" );
	
	speaker = getent( "furniture_moving_sounds_speaker", "targetname" );
	
	speaker play_sound_on_entity( "scn_estate_furniture_knock_over" );
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
	
	wait 3;
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
	
	wait 4;
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
	
	wait 3; 
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
	
	wait 5;
	
	speaker play_sound_on_entity( "scn_estate_furniture_slide" );
}

house_extra_breachguys()
{
	//These are guys that spawn in on slow-motion breaches without a special animation, and do normal cool-looking AI behavior
	
	assertEX( isdefined( self.script_slowmo_breach ), "breach_normalguy in house must have a script_slowmo_breach keypair" );
	
	level waittill ( "breaching_number_" + self.script_slowmo_breach );
	
	wait 2;
		
    self stalingradSpawn();
}

house_extra_breachguys_init()
{
	self endon ( "death" );
	
	assertEX( isdefined( self.script_namenumber), "Script_namenumber with floorname is missing on this normalguy." );
	
	self.pathrandompercent = 0;
	self.ignoresuppression = true;
	self.grenadeammo = 0;
	
	if( isdefined( self.script_delay ) )
	{
		self.goalradius = 128;
		wait self.script_delay;
		self.goalradius = 1000;
	}
	
	self thread house_normalguy_accuracy();
		
	if( !isdefined( self.script_noteworthy ) )
		return;
	
	if( self.script_noteworthy == "hunter" )
	{
		wait randomfloatrange( 5, 10 );
		self.goalradius = 10000;
	}
		
	if( self.script_noteworthy == "ambusher" )
	{
		self.combatmode = "ambush";	
	}
}

house_normalguy_accuracy()
{
	self endon ( "death" );
	
	self.baseAccuracy = 0;	
	
	wait 3;
	
	self.baseAccuracy = 1;
}

//*************************************************
// EXTERIOR BREACH SPECIFIC EXTRA GUY SPAWNS
//*************************************************

//These only spawn under very specific circumstances where it would be plausible,
//taking into consideration where the player and friendlies may already have gone.
//They are controlled by triggering by script_flag_set trigs in the main_house prefab
//They are unilaterally canceled when the armory gets breached because being able to 
//see into every room complicates the spawn plausibility situation.

//trigger for diningroom spawners "breach0_diningroom_spawntrig": these guys emerge from the dining room and rush into the kitchen


//0. (DONE) make sure the triggers only work for the correct breachnum with a script_flag_true "breaching_number_0"
//1. (DONE) run tally on extraguys' script_battleplan
//			- add to floorpops (floorpop ++ 1 time per spawner here for theoretical presence)
//2. (DONE) run spawnfuncs on extraguys (ends if 3 runs)
//			- when spawned, add to floorpops (floorpop ++ 1 time here) 
//				- also notify to terminate the cancel thread for the battleplan
//			- on death, subtract 2 from floorpops (floorpop -- 1 time here, --1 for theoretical presence)
//3. (DONE) run a cancel thread for each battleplan (ends if 2 runs)
//			- if cancel flag gets set
//			- flag_trues on triggers will automatically prevent spawntriggers from triggering (DONE)
//			- tally spawners for this battleplan per floortype and subtract size of each array from each floorpops (floorpop --1 per spawner for theoretical presence)

house_extras_tally( battlePlanName, waitName )
{
	//Because they may or may not spawn, we pre-emptively add potential spawning Extras to the total count for each floor
	
	level waittill ( waitName );
	
	//spawners = GetSpawnerArray();
	
	spawners = getentarray( "breach_extraguy", "targetname" );
	
	foreach( spawner in spawners )
	{
		if( !isdefined( spawner.script_battleplan ) )
			continue;
			
		if( spawner.script_battleplan == battlePlanName )
		{
			assertEX( isdefined( spawner.script_namenumber ), "spawner for extras in house didn't have a script_namenumber for floortype" );
			
			if( spawner.script_namenumber == "mainfloor" )
			{
				level.mainfloorPop++;
			}
			
			if( spawner.script_namenumber == "topfloor" )
			{
				level.topfloorPop++;
			}
			
			if( spawner.script_namenumber == "basement" )
			{
				level.basementPop++;
			}		
		}
	}
}

house_extras_stopcancel()
{
	//spawnfunc for the extraguys
	//if the extraguy spawns, send a notify that terminates the thread that would artificially decrement the battleplan's spawners from floorpop
	
	assertEX( isdefined( self.script_battleplan ), "script_battleplan not specified on Extra spawner in house" );
	
	endonMsg = self.script_battleplan;
	
	level notify ( endonMsg );
}

house_extras_cancel_and_killswitch( endonMsg, activeCancelName, unlockName )
{
	//shuts down an active Extras spawner system that is still ready to cancel from standby mode
	//also cancels this Extra spawner system because a non-matching exterior breach was done
	
	level endon ( endonMsg );
	
	flag_wait( unlockName );
	
	flag_wait( "armory_breached" );
	flag_set( activeCancelName );
}

house_extras_cancel_floorpop_monitor( endonMsg, activeCancelName, unlockName )
{
	//if canceled, this reduces the floorpops associated with the spawners on this battleplan
	
	assertEX( isdefined( endonMsg ), "No endonMsg was passed into this function." );
	assertEX( isdefined( activeCancelName ), "No activeCancelName was passed into this function." );
	assertEX( isdefined( unlockName ), "No unlockName was passed into this function. This is the flag that is set by an exterior breach. e.g. foyer_breached_first" );
	
	level endon ( endonMsg );	//ends on a successful spawning
	
	thread house_extras_cancel_and_killswitch( endonMsg, activeCancelName, unlockName );
	
	flag_wait( activeCancelName );
	
	spawners = getspawnerarray();
	
	foreach( spawner in spawners )
	{
		if( !isdefined( spawner.script_battleplan ) )
			continue;
			
		if( spawner.script_battleplan == endonMsg )
		{
			assertEX( isdefined( spawner.script_namenumber), "Missing a script_namenumber on an Extra spawner in house." );
			
			if( spawner.script_namenumber == "mainfloor" )
			{
				//level.mainfloorPop--;
				level notify ( "mainfloor_enemy_killed" );
			}
			
			if( spawner.script_namenumber == "topfloor" )
			{
				level notify ( "topfloor_enemy_killed" );
				//level.topfloorPop--;
			}
			
			if( spawner.script_namenumber == "basement" )
			{
				level notify ( "basement_enemy_killed" );
				//level.basementPop--;
			}
		}
		
		wait 0.05;
	}
}

house_extras_spawncontrol( spawntrigname, customEndonMsg, unlockMsg, battleplan )
{
	assertEX( isdefined( spawntrigname ), "Missing a spawntrigname on an Extra spawntrig." );
	assertEX( isdefined( unlockMsg ), "Missing an unlockMsg on an Extra spawntrig." );
	assertEX( isdefined( battleplan ), "Missing a battleplan on an Extra spawntrig." );
	
	//*NOTE* It is assumed there is only one spawntrig for each Extras spawn setup
	
	trigs = getentarray( spawntrigname, "targetname" );
	
	assertEX( trigs.size, "No triggers exist to make the Extra guys spawn on this battleplan!" );
	assertEX( trigs.size == 1, "There should only be one trigger per Extra guys battleplan, make it a big compound one if necessary." );
	
	trigs = undefined;
	
	level endon ( "house_interior_breaches_done" );
	
	if( isdefined( customEndonMsg ) )
	{
		level endon ( customEndonMsg );
	}
	
	level waittill ( unlockMsg );
	
	trig = getent( spawntrigname, "targetname" );
	trig waittill ( "trigger" );
	
	thread house_extras_spawn( battleplan );
}

house_extras_spawn( battlePlan )
{
	assertEX( isdefined( battlePlan ), "script_battleplan for the group to be spawned needs to be passed into this function" );
	
	spawners = [];
	spawners = getspawnerarray();
	
	guys = [];
	
	foreach( spawner in spawners )
	{
		if( !isdefined( spawner.script_battleplan ) )
			continue;
		
		if( spawner.script_battleplan == battlePlan )
		{
			guys[ guys.size ] = spawner;
		}
	}
	
	array_thread( guys, ::spawn_ai );  
}

house_extras_bathroom_screamingguy_setup()
{
	spawners = getentarray( "breach_extraguy", "targetname" );
	foreach( spawner in spawners )
	{
		if( !isdefined( spawner.script_battleplan ) )
			continue;
			
		if( spawner.script_battleplan == "breach0_bathroomrush" )
		{
			spawner thread add_spawn_function( ::house_extras_bathroom_screamingguy );
		}
	}
}

house_extras_bathroom_screamingguy()
{
	//aaaaAAAAA (enemy charge)
	self thread play_sound_on_entity( "est_ru1_attack" );
}

//******************************************************
// HOUSE EXTERIOR BREACH BASIC NOTIFICATIONS
//******************************************************

house_extras_breach_mainfloor()
{
	level waittill ( "breaching_number_0" );
	
	flag_set( "foyer_breached_first" );
}

house_extras_breach_kitchen()
{
	level waittill ( "breaching_number_1" );
	
	flag_set( "kitchen_breached_first" );
}

house_extras_breach_basement()
{
	level waittill ( "breaching_number_2" );
	
	flag_set( "basement_breached_first" );
}

house_exterior_breach_awareness()
{
	level waittill_any( "breaching_number_0", "breaching_number_1", "breaching_number_2" );
	
	//autosave_by_name( "exterior_breach_save" );
	
	wait 2.5;
	
	allies = getaiarray( "allies" );
	enemies = getaiarray( "axis" );
	foreach( enemy in enemies )
	{
		level.scarecrow getenemyinfo( enemy );
		level.ozone getenemyinfo( enemy );
		
		foreach( ally in allies )
		{
			enemy getenemyinfo( ally );
			enemy getenemyinfo( level.player );	
		}
	}
}

//******************************************************
// HOUSE FLOOR POP MONITORING FOR DIALOGUE TRIGGERING
//******************************************************

house_floorpop_deathmonitor_init( extraGuy )
{	
	assertEX( isdefined( self.script_namenumber), "House spawner guy missing a script_namenumber" );
	
	//don't add any of the interior breach guys (3, 4, 5), they are preemptively included in the count because they spawn after the exterior breaches

	if( self.script_namenumber == "mainfloor" )
    {
    	level.mainfloorPop++;
    	self thread house_mainfloor_deathmonitor( extraGuy );
    }
    
    if( self.script_namenumber == "topfloor" )
    {
    	if( isdefined( self.script_slowmo_breach ) )
    	{
	    	if( self.script_slowmo_breach != 5 )
	    	{
	    		level.topfloorPop++;
	    	}
    	}
	    else
	    {
	    	level.topfloorPop++;
	    }	
	    
    	self thread house_topfloor_deathmonitor( extraGuy );
    }
    
    if( self.script_namenumber == "basement" )
    {
    	if( isdefined( self.script_slowmo_breach ) )
    	{
	    	if( self.script_slowmo_breach != 3 && self.script_slowmo_breach != 4 )
	    	{
	    		level.basementPop++;
	    	}
    	}
    	else
    	{
    		level.basementPop++;
    	}
	    	
    	self thread house_basement_deathmonitor( extraGuy );
    }
}

house_mainfloor_deathmonitor( extraGuy )
{
	self waittill ( "death" );
	level notify ( "mainfloor_enemy_killed" );
	
	wait 0.05;
	
	if( isdefined( extraGuy ) )
	{
		level notify ( "mainfloor_enemy_killed" );
	}
}

house_topfloor_deathmonitor( extraGuy )
{
	self waittill ( "death" );
	level notify ( "topfloor_enemy_killed" );
	
	wait 0.05;
	
	if( isdefined( extraGuy ) )
	{
		level notify ( "topfloor_enemy_killed" );
	}
}

house_basement_deathmonitor( extraGuy )
{
	self waittill ( "death" );	
	level notify ( "basement_enemy_killed" );
	
	wait 0.05;
	
	if( isdefined( extraGuy ) )
	{
		level notify ( "basement_enemy_killed" );
	}
}

house_mainfloor_cleared()
{
	//wait until the first breach begins
	
	flag_wait( "house_exterior_has_been_breached" );
	
	wait 2.1;
	
	while( level.mainfloorPop > 0 )
	{
		level waittill ( "mainfloor_enemy_killed" );
		level.mainfloorPop--;
	}
	
	flag_set( "mainfloor_cleared" );
}

house_topfloor_cleared()
{
	//wait until the first breach begins
	
	flag_wait( "house_exterior_has_been_breached" );
	
	while( level.topfloorPop > 0 )
	{
		level waittill ( "topfloor_enemy_killed" );
		level.topfloorPop--;
	}
	
	flag_set( "topfloor_cleared" );
}

house_basement_cleared()
{
	//wait until the first breach begins
	
	flag_wait( "house_exterior_has_been_breached" );
	
	while( level.basementPop > 0 )
	{
		level waittill ( "basement_enemy_killed" );
		level.basementPop--;
	}
	
	flag_set( "basement_cleared" );
}

//*************************************************
// HOUSE CLEARING DIALOGUE
//*************************************************

house_mainfloor_cleared_dialogue()
{
	level endon ( "house_interior_breaches_done" );
	
	flag_wait( "mainfloor_cleared" );
	
	/*
	//Main floor clear!
	radio_dialogue( "est_scr_mainfloor" );

	//Copy that, main floor clear!
	radio_dialogue( "est_gst_mainfloor" );
	*/
	
	flag_set( "mainfloor_cleared_confirmed" );
}

house_topfloor_cleared_dialogue()
{
	//flag_wait( "mainfloor_cleared" );
	
	//if mainfloor is cleared
	//teleport scarecrow to the node upstairs to confirm clearance
	//then have him proceed downstairs automatically back to his node
	
	flag_wait( "topfloor_breached" );
	
	level.scarecrow disable_ai_color();
	//node = getnode( "ozone_housebriefing_start", "targetname" );
	node = getnode( "scarecrow_teleport_closer", "targetname" );
	level.scarecrow forceTeleport( node.origin, node.angles );
	level.scarecrow.goalradius = 32;
	//level.scarecrow setgoalnode( node );
	
	level.scarecrow.attackeraccuracy = 0;
	level.scarecrow.ignorerandombulletdamage = 1;
	
	node = getnode( "house_teleport_scarecrow", "targetname" );
	level.scarecrow setgoalnode( node );

	flag_wait( "topfloor_cleared" );
	
	flag_set( "scripted_dialogue_on" );
	
	wait 2;
	
	flag_waitopen( "dialogue_ghost_orders" );
	flag_waitopen( "dialogue_basement_cleared" );
	
	flag_set( "dialogue_topfloor_cleared" );
	
	//autosave_by_name( "topfloor_cleared" );

	//Top floor clear!	
	radio_dialogue( "est_scr_topfloor" );

	//Roger that, top floor clear!
	radio_dialogue( "est_gst_topfloor" );
	
	flag_set( "ghost_goes_outside" );
	
	flag_clear( "scripted_dialogue_on" );
	flag_clear( "dialogue_topfloor_cleared" );
	
	if( !flag( "basement_cleared" ) )
	{
		//node = getnode( "scarecrow_breachhouse_start", "targetname" );
		node = getnode( "scarecrow_guard_basement1", "targetname" );
		level.scarecrow setgoalnode( node );
	}
}

house_basement_cleared_dialogue()
{
	flag_wait( "basement_cleared" );
	
	wait 2;
	
	flag_waitopen( "dialogue_ghost_orders" );
	flag_waitopen( "dialogue_topfloor_cleared" );
	
	flag_set( "dialogue_basement_cleared" );
	flag_set( "scripted_dialogue_on" );
	
	//if( !flag( "topfloor_breached" ) )
		//autosave_by_name( "basement_cleared" );
	
	//Basement clear!	
	radio_dialogue( "est_scr_basement" );
	
	//Copy, basement clear!
	radio_dialogue( "est_gst_basement" );
	
	flag_clear( "scripted_dialogue_on" );
	flag_clear( "dialogue_basement_cleared" );
	
	flag_set( "basement_cleared_confirmed" );	
}

house_topfloor_breached()
{
	level waittill ( "breaching_number_" + "5" );
	
	//autosave_by_name( "topfloor_breach_save" );
	//autosave_now();
	
	flag_set( "topfloor_breached" );
	level.interiorRoomsBreached++;
}

house_basement_breached_armory()
{	
	level waittill ( "breaching_number_" + "3" );
	
	//autosave_by_name( "armory_breach_save" );
	//autosave_now();
	
	flag_set( "basement_breached" );
	flag_set( "armory_breached" );
	level.interiorRoomsBreached++;
}

house_basement_breached_guestroom()
{
	level waittill ( "breaching_number_" + "4" );
	
	//autosave_by_name( "guestroom_breach_save" );
	//autosave_now();
	
	flag_set( "basement_breached" );
	flag_set( "guestroom_breached" );
	level.interiorRoomsBreached++;
}

house_check_upstairs_mainfloor_dialogue()
{
	level endon ( "house_interior_breaches_done" );
	
	flag_wait( "house_friendlies_instructions_given" );
	
	while( !flag( "topfloor_breached" ) )
	{
		//if main floor is cleared
		//AND top breach is not yet done
		
		tracePassed = level.ghost SightConeTrace( level.player.origin + ( 0, 0, 64 ) );
				
		if( tracePassed && flag( "ghost_at_bottom_of_stairs" ) )
		{
			flag_waitopen( "dialogue_ghost_orders" );
			flag_waitopen( "dialogue_topfloor_cleared" );
			flag_waitopen( "dialogue_basement_cleared" );
			
			if( flag( "scarecrow_said_upstairs" ) )
				wait 30;
			
			if( !flag( "topfloor_breached" ) )
			{
				flag_set( "scripted_dialogue_on" );
				
				//Roach, go upstairs and check any locked rooms on the top floor. Breach and clear.	
				radio_dialogue( "est_gst_lockedrooms" );
			
				flag_clear( "scripted_dialogue_on" );
			}
		
			break;
		}
		
		wait 0.5;
	}
}

house_check_upstairs_basement_dialogue()
{
	level endon ( "house_interior_breaches_done" );
	
	flag_wait( "basement_breached" );
	
	//flag_waitopen( "breaching_on" );

	//if basement is cleared, and top breach is not yet done
		
	//run scarecrow to basement room
		
	node = getnode( "scarecrow_guard_basement2", "targetname" );
	level.scarecrow disable_ai_color();
	level.scarecrow setgoalnode( node );
	level.scarecrow.goalradius = 16;
	level.scarecrow waittill ( "goal" );
	
	if( !flag( "basement_cleared" ) )
	{
		flag_waitopen( "dialogue_ghost_orders" );	
		flag_waitopen( "dialogue_topfloor_cleared" );	
		flag_waitopen( "dialogue_basement_cleared" );
		
		if( !flag( "basement_cleared" ) )
		{	
			flag_set( "scripted_dialogue_on" );
			
			//I got your back, Roach.	
			radio_dialogue( "est_scr_gotyourback" );
			
			flag_clear( "scripted_dialogue_on" );
		}
	}
	
	if( !flag( "topfloor_breached" ) )
	{
		flag_wait( "basement_cleared_confirmed" );
		
		if( !flag( "house_interior_breaches_done" ) )
		{
			//tracePassed = sighttracepassed( level.player.origin, level.scarecrow.origin, true, undefined );
			
			while( !flag( "topfloor_breached" ) )
			{
				tracePassed = level.scarecrow SightConeTrace( level.player.origin + ( 0, 0, 64 ) );
				
				if( tracePassed )
				{
					flag_waitopen( "dialogue_ghost_orders" );
					flag_waitopen( "dialogue_topfloor_cleared" );
					flag_waitopen( "dialogue_basement_cleared" );
					
					flag_set( "scripted_dialogue_on" );
					
					flag_set( "scarecrow_said_upstairs" );
					
					//I've got this area covered Roach. Get upstairs and check the rooms on the top floor.	
					radio_dialogue( "est_scr_getupstairs" );
			
					flag_clear( "scripted_dialogue_on" );
					
					break;
				}
				
				wait 0.5;
			}
		}
	}
}

house_check_basement_dialogue()
{
	flag_wait( "mainfloor_cleared" );
	flag_wait( "topfloor_cleared" );
	
	wait 4;
	
	if( !flag( "house_interior_breaches_done" ) )
	{
		if( !flag( "basement_cleared" ) )
		{
			//if topfloor is cleared
			//AND if mainfloor is cleared
			//AND basement is not cleared
			
			flag_set( "scripted_dialogue_on" );
			
			level.scarecrow.doorFlashChance = 1;
			
			enemies = getaiarray( "axis" );
			foreach( enemy in enemies )
			{
				level.scarecrow getenemyinfo( enemy );
			}
			
			flag_waitopen( "dialogue_ghost_orders" );
			flag_waitopen( "dialogue_topfloor_cleared" );
			flag_waitopen( "dialogue_basement_cleared" );
			
			//Roach, check the basement for enemy activity. Breach and clear. Go.	
			radio_dialogue( "est_gst_checkbasement" );
			
			flag_clear( "scripted_dialogue_on" );
		}
	}
}

house_clearing_banter()
{
	level endon ( "house_interior_breaches_done" );
	
	//if main floor is cleared
	//AND at least one interior breach remains
	
	flag_wait( "mainfloor_cleared_confirmed" );
	
	flag_set( "scripted_dialogue_on" );
	
	flag_waitopen( "dialogue_topfloor_cleared" );
	flag_waitopen( "dialogue_basement_cleared" );
	flag_set( "dialogue_ghost_orders" );
			
	wait 2;
	
	//Ozone now navigates to a final guard node inside the kitchen
		
	node = getnode( "ozone_guard_kitchen", "targetname" );
	level.ozone disable_ai_color();
	level.ozone setgoalnode( node );
	
	if( !flag( "topfloor_breached" ) || !flag( "basement_breached" ) )
	{			
		lines = [];
		lines[ lines.size ] = "est_gst_thrukitchen";	//Ozone, make sure no one leaves through the kitchen.	
		lines[ lines.size ] = "est_ozn_rogerthat";		//Roger that.	
		lines[ lines.size ] = "est_gst_sitrep";			//Scarecrow, gimme a sitrep.	
		lines[ lines.size ] = "est_scr_noonesleaving";	//No one's leaving through the front of the basement.
		
		foreach( index, line in lines )
		{
			flag_waitopen( "breaching_on" );
			radio_dialogue( lines[ index ] );
		}
	}
	
	flag_clear( "scripted_dialogue_on" );
	
	flag_clear( "dialogue_ghost_orders" );
	
	flag_set( "house_friendlies_instructions_given" );
}

house_battlechatter_check()
{
	//level endon ( "house_interior_breaches_done" );
	//level endon ( "all_enemies_killed_up_to_house_capture" );
	
	flag_wait( "breaching_on" );
	
	level.ghost.voice = "seal";
	level.ghost.countryID = "NS";
	
	level.scarecrow.voice = "seal";
	level.scarecrow.countryID = "NS";
	
	level.ozone.voice = "seal";
	level.ozone.countryID = "NS";
	
	while( !flag( "all_enemies_killed_up_to_house_capture" ) )
	{
		battlechatter_off( "allies" );
		battlechatter_off( "axis" );
		
		flag_waitopen( "breaching_on" );
		flag_waitopen( "scripted_dialogue_on" );
		
		battlechatter_on( "allies" );
		battlechatter_on( "axis" );
	
		flag_wait_any( "breaching_on", "scripted_dialogue_on" ); 
	}
}

house_autosaves()
{
	if ( flag( "all_enemies_killed_up_to_house_capture" ) )
		return;
		
	level endon ( "all_enemies_killed_up_to_house_capture" );
	
	level waittill_any( "breaching_number_0" , "breaching_number_1" , "breaching_number_2" );
	
	saveTrig = getent( "house_clearing_autosave_trigger", "targetname" );
	
	while ( 1 )
	{
		//if nothing inside the house has been breached, allow a save every 30 seconds if player touches trigger
		
		if( level.interiorRoomsBreached > 0 )
		{
			if( !flag( "first_free_save" ) )
			{
				level waittill ( "slomo_breach_over" );
			}
			
			saveTrig waittill ( "trigger" );
	
			autosave_by_name( "nearDoorBreach_save" );	
			
			flag_clear( "first_free_save" );
		}
		else
		{
			saveTrig waittill ( "trigger" );
			autosave_by_name( "nearDoorBreach_save" );
			
			house_autosave_timeout();	//interruptible debounce if breach is done
			
			flag_set( "first_free_save" );
			
			continue;
		}
	}
}

house_autosave_timeout()
{
	level endon ( "slomo_breach_over" );
	
	//level endon ( "breaching_number_3" );
	//level endon ( "breaching_number_4" );
	//level endon ( "breaching_number_5" );
	
	wait 30;
}

house_ghost_sweep()
{	
	//Ghost's basic movement through the house during the clearing sequence

	flag_set( "no_mercy" );

	level endon ( "house_reset_ghost" );
	
	level waittill_any( "breaching_number_0" , "breaching_number_1" , "breaching_number_2" );
	
	if( flag( "kitchen_breached_first" ) || flag( "basement_breached_first" ) )
	{
		thread house_ghost_sweep_trigger_timeout();
		thread house_ghost_sweep_trigger_manual();
	}
	
	level.ghost disable_ai_color();
	
	level.ghost.attackeraccuracy = 0;
	level.ghost.ignorerandombulletdamage = 1;
	level.ghost disable_pain();
	level.ghost disable_surprise();
	level.ghost.disableBulletWhizbyReaction = 1;
	
	level.ghost disable_arrivals();
	
	level.ghost.dontmelee = true;
	
	if( flag( "foyer_breached_first" ) )
	{	
		level waittill ( "sp_slowmo_breachanim_done" );
		
		level.ghost.goalradius = 64;
		
		magicSpot = getent( "ghost_slowmo_entry_teleport", "targetname" );
		level.ghost forceTeleport( magicSpot.origin, magicSpot.angles );
		
		level.ghost enable_heat_behavior( 1 );
		
		node = getnode( "ghost_slowmo_entry", "targetname" );
		
		setsaveddvar( "ai_friendlyFireBlockDuration", "0" );
		
		level.ghost setgoalnode( node );
		level.ghost waittill ( "goal" );

		wait 8;
	}
	else
	{
		flag_wait( "ghost_begins_sweep" );
		
		level.ghost delaythread( 3, ::enable_heat_behavior, 1 );
	}
	
	node = getnode( "ghost_house_sweep", "targetname" );
	
	nodecount = 0;
	
	while( 1 )
	{		
		level.ghost setgoalnode( node );
		level.ghost waittill ( "goal" );	
		
		setsaveddvar( "ai_friendlyFireBlockDuration", "2000" );
		
		if( nodecount == 0 )
		{
			wait 4;
			level.ghost.goalradius = 32;
		}
		
		if( nodecount == 1 )
		{
			wait 0.5;
			
			flag_set( "scripted_dialogue_on" );
			
			flag_waitopen( "dialogue_ghost_orders" );
			flag_waitopen( "dialogue_topfloor_cleared" );
			flag_waitopen( "dialogue_basement_cleared" );
			
			//Office clear!	
			radio_dialogue( "est_gst_officeclear" );
			
			//Clear!
			//radio_dialogue( "est_gst_clear" );
			
			wait 0.5;
			
			flag_waitopen( "dialogue_ghost_orders" );
			flag_waitopen( "dialogue_topfloor_cleared" );
			flag_waitopen( "dialogue_basement_cleared" );
			
			//Let's go, let's go!
			delaythread( 1, ::radio_dialogue, "est_gst_letsgo2" );
			
			flag_clear( "scripted_dialogue_on" );
		}
	
		nodecount++;
		
		if( nodecount == 4 )
		{
			wait 0.25;
			
			flag_waitopen( "dialogue_ghost_orders" );
			flag_waitopen( "dialogue_topfloor_cleared" );
			flag_waitopen( "dialogue_basement_cleared" );
			
			flag_set( "scripted_dialogue_on" );
		
			//Dining room clear!
			radio_dialogue( "est_gst_diningroomclr" );
			
			flag_clear( "scripted_dialogue_on" );
		}
		
		if( nodecount == 5 )
		{
			level.ghost disable_heat_behavior();	
			level.ghost enable_arrivals();
		}

		if( isdefined( node.target ) )
		{
			node = getnode( node.target, "targetname" );
		}
		else
		{
			break;
		}
	}	
	
	ent = getent( "ghost_fake_lookat", "targetname" );
	level.ghost cqb_aim( ent );
	
	flag_set( "ghost_at_bottom_of_stairs" );
	
	flag_wait( "topfloor_cleared" );
	
	if( !flag( "ghost_goes_outside" ) )
	{
		wait 9.5;
	}
	
	node = getnode( "ghost_cover_front", "targetname" );
	level.ghost setgoalnode( node );
}

house_ghost_sweep_trigger_timeout()
{
	level endon ( "house_reset_ghost" );
	
	//Send Ghost automatically if player never hits the trigger
	
	wait 30;
	
	flag_set( "ghost_begins_sweep" );
}

house_ghost_sweep_trigger_manual()
{
	level endon ( "house_reset_ghost" );
	
	manualTrig = getent( "ghost_manual_trig", "targetname" );
	manualTrig waittill ( "trigger" );
	
	flag_set( "ghost_begins_sweep" );
}

house_ghost_lastbreach_reset()
{
	//on the final interior breach, sends Ghost back to his spot outside the front door for the talking animation
	
	level waittill_any( "breaching_number_0" , "breaching_number_1" , "breaching_number_2" );	//exterior
	
	level waittill_any( "breaching_number_3" , "breaching_number_4" , "breaching_number_5" );	//interior 1
		
	level waittill_any( "breaching_number_3" , "breaching_number_4" , "breaching_number_5" );	//interior 2
	
	level waittill_any( "breaching_number_3" , "breaching_number_4" , "breaching_number_5" );	//interior 3
	
	level notify ( "house_reset_ghost" );
	
	level.ghost enable_ai_color();
}

//********************
// SOLAR PANELS
//********************

solar_panels()
{
	panels = getentarray( "solar_panel", "targetname" );
	panels_colmaps = getentarray( "solar_panel_collision", "targetname" );
	
	array_thread( panels, ::solar_panels_rotate );
	array_thread( panels_colmaps, ::solar_panels_rotate );
}

solar_panels_rotate()
{
	//self RotateYaw( 45, 180, 3, 3 );
	
	flag_wait( "forestfight_littlebird_1" );
	
	wait 3;
	
	self RotateYaw( -95, 60, 3, 3 );
}

//********************
// MUSIC
//********************

estate_music()
{	
	switch( level.start_point )
	{
		case "default":
		case "ambush":
		case "forestfight":
		case "house_approach":
		case "house_breach":
			flag_wait( "start_ambush_music" );
			thread MusicLoop( "estate_ambushfight" ); // 2 minutes 18 seconds
			
			flag_wait ( "all_enemies_killed_up_to_house_capture" );
		
			level notify( "stop_music" );
			musicStop( 5 );
			wait 5.1;
		
		case "house_briefing":
			thread MusicLoop( "estate_basement_clear" ); // 1 minutes 38 seconds
		
			flag_wait( "download_started" );
		
			level notify( "stop_music" );
			musicStop( 6 );
			wait 6.1;
		
		case "house_defense":
			musicPlayWrapper( "estate_dsm_wait" );
		
			flag_wait( "download_complete" );
			musicStop( 1 );
			wait 1.1;
			
		case "escape":		
			thread MusicLoop( "estate_escape" ); // 2 minutes 4 seconds
		
			flag_wait( "finish_line" );
		
			level notify( "stop_music" );
			musicStop();
			
		case "ending":
			flag_wait( "begin_ending_music" );
			wait 9;
			musicPlayWrapper( "estate_betrayal" );
			break;
	}
}

temp_text( text )
{
        iPrintln( text );
}

//*****************************
// PLAYER ZONE DETECTION
//*****************************

zone_detection_init()
{
	level.playerZone = undefined;
	
	zoneTriggers = getentarray( "zone_trigger", "script_noteworthy" );
	array_thread( zoneTriggers, ::zone_detection );
}

zone_detection()
{
	timePassed = 0;
	
	while( 1 )
	{
		self waittill( "trigger" );
		
		assertEX( isdefined( self.targetname ) );
	
		level.playerZone = self.targetname;
			
		//iprintln( level.playerZone );
	}
}

/*
Strike Package concept

A preset themed attack pattern based on known player location
	- mass helicopter troop assault
	- faked mass troop drop in the start area and solar panels for a double envelopment assault from the east, player is near house
	- mass troop smoke assault from the southeast with the fence getting blown up and breached
	- vehicle assault from the road, unloading lots of enemy troops
	- troop drop at the boathouse
	- mass troop smoke assault from the northeast woods
	
28 enemy troops in a strike package, no respawns

Need a way to control these options based on player position, so player does not witness spawning

Rolling Strike concept

After a Strike Package is deployed, it is supplemented by a Rolling Strike for some amount of time.
During the Rolling Strike, the player is not allowed to stray from the building too far.
Strike Components are deployed a la carte from a 'menu' of 'one-time pads'
	- MI-17 helicopter 8-man deployment
	- MD500 helicopter 4-man deployment
	- UAZ jeep 6-man deployment (limited supply due to pathing options)
	- ZIL truck 8-man deployment (limited supply due to pathing options)
	- Remote magic spawned 4-man Hunter-killer team (unlimited supply) with helicopter for show and smoke grenade action
	- Smokespawned 8-man assault team (unlimited supply) with smoke grenade action
	- Magic spawned 2-man Sniper team
	
No components are called twice during the Rolling Strike, there are many variants for each but quantity 1 for each
	- need to place many alternate positions that are player position sensitive
	
	
1. Need to set up strike packages.
		- each is a separate function
			- these are activated like Strike Components, player reactive, player goes anywhere			
2. Need to know which zone player is in at all times.
		- use waittill trigger on back-to-back triggers
3. Need to place a whole bunch of Strike Components in the level to support the strike packages during rolling strikes.
		- each zone needs to have corresponding Strike Components to handle visibility to the player
4. For the final field and the outer road, we need a failure cordon to keep the player from running into the wild
		- failure cordon
			- 1. don't get too far from the house warning shout + timer to fail begins unless retriggered
			- 2. we're doomed, failsafe trigger, automatic lose
			
Zones:

script_noteworthy: zone_trigger

targetnames:

zone_backsolarpanelfield

zone_backyardpond 
zone_backyardshed
zone_stables
zone_birchfield

zone_house
zone_forest
zone_frontsolarpanels
zone_parkinglot
zone_porchtriangle
zone_porch
zone_frontyardwedge
zone_frontyardhigh

zone_backpatio
zone_beach
zone_boathouse

*/

defense_schedule_control()
{	
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	flag_wait( "defense_battle_begins" );

	level.packageThresholdPop = 0;
	level.packageComponentThresholdPop = 14;	//the enemy pop at which we allow strikeComponents to be deployed during a strikePackage
	level.enemyTotalPop = 0;
	level.componentCycleLimit = 1;
	level.packageTime = undefined;

	//Every playerZone has a corresponding list of strikePackages
	
	level.strikePackage = [];

	addStrikePackage( "zone_backsolarpanelfield", ::strike_package_bighelidrop );
	addStrikePackage( "zone_backsolarpanelfield", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_backsolarpanelfield", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_backsolarpanelfield", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_backyardshed", ::strike_package_bighelidrop );
	addStrikePackage( "zone_backyardshed", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_backyardshed", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_backyardshed", ::strike_package_solarfield );
	addStrikePackage( "zone_backyardshed", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_stables", ::strike_package_bighelidrop );
	addStrikePackage( "zone_stables", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_stables", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_stables", ::strike_package_solarfield );
	addStrikePackage( "zone_stables", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_birchfield", ::strike_package_bighelidrop );
	addStrikePackage( "zone_birchfield", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_birchfield", ::strike_package_solarfield );
	addStrikePackage( "zone_birchfield", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_forest", ::strike_package_bighelidrop );
	addStrikePackage( "zone_forest", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_forest", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_frontsolarpanels", ::strike_package_bighelidrop );
	addStrikePackage( "zone_frontsolarpanels", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_frontsolarpanels", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_backyardpond", ::strike_package_bighelidrop );
	addStrikePackage( "zone_backyardpond", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_backyardpond", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_backyardpond", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_house", ::strike_package_bighelidrop );
	addStrikePackage( "zone_house", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_house", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_house", ::strike_package_solarfield );
	addStrikePackage( "zone_house", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_parkinglot", ::strike_package_bighelidrop );
	addStrikePackage( "zone_parkinglot", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_parkinglot", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_parkinglot", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_porchtriangle", ::strike_package_bighelidrop );
	addStrikePackage( "zone_porchtriangle", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_porchtriangle", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_porchtriangle", ::strike_package_solarfield );
	addStrikePackage( "zone_porchtriangle", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_porch", ::strike_package_bighelidrop );
	addStrikePackage( "zone_porch", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_porch", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_porch", ::strike_package_solarfield );
	addStrikePackage( "zone_porch", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_frontyardwedge", ::strike_package_bighelidrop );
	addStrikePackage( "zone_frontyardwedge", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_frontyardwedge", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_frontyardwedge", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_frontyardhigh", ::strike_package_bighelidrop );
	addStrikePackage( "zone_frontyardhigh", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_frontyardhigh", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_frontyardhigh", ::strike_package_solarfield );
	addStrikePackage( "zone_frontyardhigh", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_backpatio", ::strike_package_bighelidrop );
	addStrikePackage( "zone_backpatio", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_backpatio", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_backpatio", ::strike_package_solarfield );
	addStrikePackage( "zone_backpatio", ::strike_package_boathouse_helidrop );
	
	addStrikePackage( "zone_beach", ::strike_package_bighelidrop );
	addStrikePackage( "zone_beach", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_beach", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_beach", ::strike_package_solarfield );
	
	addStrikePackage( "zone_boathouse", ::strike_package_bighelidrop );
	addStrikePackage( "zone_boathouse", ::strike_package_birchfield_smokeassault );
	addStrikePackage( "zone_boathouse", ::strike_package_frontyard_md500_rush );
	addStrikePackage( "zone_boathouse", ::strike_package_solarfield );
	
	level.strikeComponents = [];
	
	addStrikeComponent( "zone_backsolarpanelfield", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_backsolarpanelfield", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_backsolarpanelfield", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_backyardshed", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_backyardshed", ::strike_component_rpg_team_boathouse );
	
	addStrikeComponent( "zone_stables", ::strike_component_rpg_team_boathouse );
	
	addStrikeComponent( "zone_birchfield", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_birchfield", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_forest", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_forest", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_forest", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_frontsolarpanels", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_frontsolarpanels", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_frontsolarpanels", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_backyardpond", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_backyardpond", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_house", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_house", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_house", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_parkinglot", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_parkinglot", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_parkinglot", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_porchtriangle", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_porchtriangle", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_porchtriangle", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_porch", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_porch", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_porch", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_frontyardwedge", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_frontyardwedge", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_frontyardwedge", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_frontyardhigh", ::strike_component_rpg_team_stables );
	addStrikeComponent( "zone_frontyardhigh", ::strike_component_rpg_team_boathouse );
	addStrikeComponent( "zone_frontyardhigh", ::strike_component_rpg_team_southwest );
	
	addStrikeComponent( "zone_backpatio", ::strike_component_rpg_team_stables );
	
	addStrikeComponent( "zone_beach", ::strike_component_rpg_team_stables );
	
	addStrikeComponent( "zone_boathouse", ::strike_component_rpg_team_stables );
	
	//tally up the total enemies in the package
	
	//enemies = [];
	//enemies = getaiarray( "axis" );
	level.enemyTotalPop = 0;
	
	badsave_volume = getent( "no_autosave_in_basement", "targetname" );
	safeDSMhealth = level.dsmHealthMax * 0.7;
	
	while( 1 )
	{
		//while we are in the defense phase
		//select a strike package based on player location and run it
		
		randomStrikePackage( level.playerZone );	
		
		dsm_real = getent( "dsm", "targetname" );
		playerDSMdist = length( level.player.origin - dsm_real.origin );
		
		if( flag( "can_save" ) && playerDSMdist <= level.playerDSMsafedist )
		{
			if( !( level.player istouching( badsave_volume ) ) && level.dsmHealth >= safeDSMhealth )
			{
				autosave_by_name( "strikePackageCleared" );		
			}
		}
		
		while( level.enemyTotalPop > level.packageThresholdPop )
		{
			level waittill ( "counterattacker_died" );
		}
		
		flag_set( "activate_package_on_standby" );
		
		//if all exhausted completely, bail out
		
		foreach( zone, array in level.strikePackage )
		{
			if( level.strikePackage[ zone ].size <= 0 )
			{
				level.strikePackage[ zone ] = undefined;
			}
		}
		
		wait 10;
		
		if( level.strikePackage.size <= 0 )
		{
			break;
		}
		
		flag_wait( "strike_package_spawned" );
		flag_clear( "strike_package_spawned" );
	
		//each package spawns everyone at once and the spawners autorun a deathmonitor thread on themselves
		//monitor the deathcount for the package until some threshold pop is reached
		
		while( level.enemyTotalPop > level.packageComponentThresholdPop )
		{
			level waittill ( "counterattacker_died" );
		}
		
		//when a minimum deathcount is reached
		//randomly select one-time-use Strike Components based on player location until the force cap is reached
		//keep doing this until the Strike Component usage limit is reached for this Strike Package
			//OR until the Strike Package Time on Target is expended
		
		if( !isdefined( level.packageTime ) )
		{
			level.packageTime = 30;
		}
		
		defense_component_schedule();
		
		//deploy the next strike_package
		
		wait 1;
	}
}

defense_component_schedule()
{
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	componentCount = 0;
	deployed = undefined;
	
	thread defense_component_timeout();
	
	while( level.packageTime > 0 )
	{
		//Cleanup empty arrays
		
		foreach( zone, array in level.strikeComponents )
		{
			if( level.strikeComponents[ zone ].size <= 0 )
			{
				level.strikeComponents[ zone ] = undefined;
			}
		}
		
		if( level.strikeComponents.size <= 0 )
		{
			level notify ( "stop_timeout" );
			break;
		}
		
		deployed = randomStrikeComponent( level.playerZone );
		if( isdefined( deployed ) && deployed )
		{
			flag_wait( "strike_component_activated" );
			flag_clear( "strike_component_activated" );
			
			componentCount++;
			
			while( level.enemyTotalPop > level.componentThresholdPop )
			{
				level waittill ( "counterattacker_died" );
			}
	
			flag_set( "activate_component_on_standby" );
		}
		
		if( componentCount >= level.componentCycleLimit )
		{
			level notify ( "stop_timeout" );
			break;
		}
		
		wait randomfloatrange( 2, 8 );
	}
}

defense_component_timeout()
{
	//Each package activates the release of strikeComponents for this amount of time before activating the next package
	
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	level endon ( "stop_timeout" );
	
	while( level.packageTime > 0 )
	{
		wait 1;
		level.packageTime--;
	}
}

//=================================================

addStrikePackage( zone, package )
{
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	if( !isdefined( level.strikePackage[ zone ] ) )
	{
		level.strikePackage[ zone ] = [];
	}
	level.strikePackage[ zone ][ level.strikePackage[ zone ].size ] = package;
}

randomStrikePackage( zone )
{
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	if( !isdefined( level.strikePackage[ zone ] ) )
		return( false );
	
	if( !level.strikePackage[ zone ].size )
	{
		level.strikePackage[ zone ] = undefined;
		return( false );
	}
	
	package = random( level.strikePackage[ zone ] );
	
	thread [[ package ]]();
	
	flag_set( "strike_packages_definitely_underway" );
	
	//a strikePackage is for one-time use only; this removes any cases of this package in all the strikePackage arrays
	
	foreach( zone, array in level.strikePackage )
	{	
		foreach( index, element in array )
		{
			if( element == package )
			{
				level.strikePackage[ zone ][ index ] = undefined;
			}
		}
	}
	
	return( true );
	
	//level.strikePackage[ "zone_backsolarpanelfield" ][ 0 ] = ::strike_package_bighelidrop;
	//level.strikePackage[ "zone_backsolarpanelfield" ][ 1 ] = "blah";
}

//=====================================

addStrikeComponent( zone, component )
{
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	if( !isdefined( level.strikeComponents[ zone ] ) )
	{
		level.strikeComponents[ zone ] = [];
	}
	
	level.strikeComponents[ zone ][ level.strikeComponents[ zone ].size ] = component;
}

randomStrikeComponent( zone )
{
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	
	if( !isdefined( level.strikeComponents[ zone ] ) )
		return( false );
	
	if( !level.strikeComponents[ zone ].size )
	{
		level.strikeComponents[ zone ] = undefined;
		return( false );
	}
	
	component = random( level.strikeComponents[ zone ] );
	
	thread [[ component ]]();
	
	//a strikeComponent is for one-time use only; this removes any cases of this package in all the strikePackage arrays
	
	foreach( zone, array in level.strikeComponents )
	{
		foreach( index, element in array )
		{
			if( element == component )
			{
				level.strikeComponents[ zone ][ index ] = undefined;
			}
		}
	}
	
	return( true );
}

//To do:

	//"forest_smokeassault";
	//"birchfield_smokeassault";
	//"beachlanding";
	//"forest_smokeassault";
	//"solarfield_fakedrop";

//=================================================

birchfield_exfil()
{
	flag_wait( "download_complete" );
	
	autosave_by_name( "download_done" );
	
	//Ghost goes to a good spot near the front door
	
	//*TEMP dialogue - Ghost prompts player to get the DSM
	
	if( !flag( "dsm_recovered" ) )
	{
		thread birchfield_exfil_nag();
	}

	if( flag( "skip_defense" ) )
	{
		wait 3;
	}
	
	//crank up Ghost's baseaccuracy
	
	level.ghost.baseAccuracy = 1000;
	
	//spawn token enemies for Ghost to own
	
	flag_wait( "dsm_recovered" );
	
	autosave_by_name( "birchfield_exfil_started" );
	
	wait 3;
	
	//Ghost starts using friendlychains again to get to the endzone
	
	//This is Shepherd. We're almost at the LZ. What's your status, over?	
	radio_dialogue( "est_shp_almostatlz" );
	
	//We're on our way to the LZ! Roach, let's go!!	
	level.ghost thread dialogue_queue( "est_gst_onourway" );
	
	wait 2;
	
	level.fixednodesaferadius_default = 64;
	
	level.ghost enable_ai_color();
	
	if( isalive( level.ozone ) )
	{
		level.ozone enable_ai_color();
	}
	
	if( isalive( level.scarecrow ) )
	{
		level.scarecrow enable_ai_color();
	}
	
	trig = getent( "ghost_exfil", "targetname" );
	trig notify ( "trigger" );
	
	flag_wait( "point_of_no_return" );
	
	autosave_by_name( "point_of_no_return" );
	
	thread maps\_mortar::bog_style_mortar_on( "2" );
	
	thread birchfield_mortar_playerkill();
	thread birchfield_ghost_covering_shouts();
	
	//They're bracketing our position with mortars, keep moving but watch your back!!!	
	level.ghost thread dialogue_queue( "est_gst_bracketing" );
	
	wait 1;
}

birchfield_exfil_nag()
{
	level endon ( "dsm_recovered" );
	
	delay = 7;
	
	while( 1 )
	{
		wait 2;
		
		//Roach, the transfer's complete! I'll cover the main approach while you get the DSM! Move!	
		radio_dialogue( "est_gst_dsmcomplete" );
		
		wait delay;
		
		if( delay < 30 )
		{
			delay = delay * 2;
		}
		
		//Roach! I'm covering the front! Get the DSM! We gotta get outta here!	
		radio_dialogue( "est_gst_getouttahere" );
	}
}

birchfield_mortar_playerkill()
{
	level endon ( "finish_line" );
	
	flag_wait( "player_retreated_into_birchfield" );
	
	thread mortar_in_face_killplayer();
}

birchfield_ghost_covering_shouts()
{
	//If Ghost gets to his colornodes, he triggers this line of dialogue to look like he's covering the player
	
	level endon ( "finish_line" );
	
	trigs = getentarray( "ghost_covering_shout", "targetname" );
	foreach( trig in trigs )
	{
		trig thread birchfield_ghost_shout_trigger();
	}
	
	wait 9;
	
	//We gotta get to the LZ! Roach, come on!	
	level.ghost dialogue_queue( "est_gst_gettothelz" );
}

birchfield_ghost_shout_trigger()
{
	self waittill ( "trigger" );
	
	if( !flag( "ghost_covered_player" ) )
	{
		flag_set( "ghost_covered_player" );
	
		//Roach, I got you covered!! Go! Go!!	
		level.ghost dialogue_queue( "est_gst_gotyoucovered" );
		
		//Get to the LZ! Keep moving!		
		level.ghost dialogue_queue( "est_gst_keepmoving" );
	}
	else
	{
		//I'll cover you! Move!!!		
		level.ghost dialogue_queue( "est_gst_illcoveryou" );
		
		//Go! Go!		
		level.ghost dialogue_queue( "est_gst_gogo" );
	}
}

birchfield_knockout()
{
	flag_wait( "finish_line" );
	
	level.player EnableDeathShield( true );
	level.cover_warnings_disabled = 1;
	
	level.player freezeControls( true );
	
	mortarHitOrg = playerFovGroundSpot();
	playFX( level._effect[ "mortar" ][ "dirt" ], mortarHitOrg );
	detorg = spawn( "script_origin", mortarHitOrg );
	detorg playsound( "clusterbomb_explode_default" );
	level.player PlayRumbleOnEntity( "artillery_rumble" );
	wait 0.15;

	flag_set( "play_ending_sequence" );
	
	level.player freezeControls( false );
	
	musicStop();
}

//=================================================

mortar_in_face_killplayer()
{
	mortarHitOrg = playerFovGroundSpot();
	playFX( level._effect[ "mortar" ][ "dirt" ], mortarHitOrg );
	level.player PlayRumbleOnEntity( "artillery_rumble" );
	wait 0.15;
	level.player kill();
}

//=================================================

strike_package_bighelidrop()
{
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "strike_package_bighelidrop_dialogue" );
	
	level.packageThresholdPop = 2;	//max number of AI that is allowed to be alive before deploying this group
	level.packageTime = 90;
	
	flag_wait( "activate_package_on_standby" );
	flag_clear( "activate_package_on_standby" );
	
	if( !flag( "dsm_recovered" ) )
	{
		thread strike_package_bighelidrop_dialogue();
	}
	
	heli1 = spawn_vehicle_from_targetname_and_drive( "heli_phoenix_01" );	
	heli2 = spawn_vehicle_from_targetname_and_drive( "heli_phoenix_02" );	
	heli3 = spawn_vehicle_from_targetname_and_drive( "heli_phoenix_03" );	
	heli4 = spawn_vehicle_from_targetname_and_drive( "heli_phoenix_04" );	
	
	thread defense_helidrop_rider_setup( heli1, "heli_phoenix_01" );
	thread defense_helidrop_rider_setup( heli2, "heli_phoenix_02" );
	thread defense_helidrop_rider_setup( heli3, "heli_phoenix_03" );
	thread defense_helidrop_rider_setup( heli4, "heli_phoenix_04" );
	
	//heli5 = spawn_vehicle_from_targetname_and_drive( "heli_phoenix_05" );
	
	wait 3;
	
	flag_set( "strike_package_spawned" );
}

strike_package_bighelidrop_dialogue()
{
	//Sniper Team One to strike team, be advised, we got enemy helos approaching from the northwest and southeast. 
	radio_dialogue( "est_snp1_mainroad" );
	
	//Enemy choppers in 15 seconds. 
	radio_dialogue( "est_snp1_15seconds" );
	
	//Roger that, 15 seconds!
	radio_dialogue( "est_gst_15seconds" );
	
	flag_clear( "strike_package_bighelidrop_dialogue" );
}

//=================================================

strike_package_birchfield_smokeassault()
{	
	//blow up the fence with several charges detonating, and quake the player each time
	
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "strike_package_birchfield_dialogue" );
	
	level.packageThresholdPop = 10;	//max number of AI that is allowed to be alive before deploying this group
	level.packageTime = 120;
	
	flag_wait( "activate_package_on_standby" );
	flag_clear( "activate_package_on_standby" );
	
	if( !flag( "dsm_recovered" ) )
	{
		thread strike_package_birchfield_smokeassault_dialogue();
	}
	
	fence_detonators = getstructarray( "chainlink_fence_detonator", "targetname" );
	foreach( detonator in fence_detonators )
	{
		wait randomfloatrange( 0.25, 1 );
		playFX( getfx( "fenceblast" ), detonator.origin );
		detorg = spawn( "script_origin", detonator.origin );
		detorg playsound( "clusterbomb_explode_default" );
		earthquake( 0.25, 1, level.player.origin, 2000 );
	}
	
	if( !flag( "fence_removed" ) )
	{
		fence = getent( "final_area_fence", "targetname" ); 
		fence delete();
		flag_set( "fence_removed" );
	}

	//turn on the smoke machines to hide the spawns
	
	//spawn the attack as one big batch, no respawns
	
	core_spawn( "birchfield_smokeassault_leftflank", ::birchfield_leftflank_routing );
	core_spawn( "birchfield_smokeassault_rightflank", ::birchfield_rightflank_routing );
	core_spawn( "birchfield_smokeassault_centersupport", ::birchfield_centersupport_routing );
	
	//send out the distracting MD-500 helicopters
	
	wait 3;
	
	flag_set( "strike_package_spawned" );
}

strike_package_birchfield_smokeassault_dialogue()
{
	wait 2;
	
	if( isalive( level.scarecrow ) )
	{
		//What the hell was that?
		radio_dialogue( "est_scr_whatwasthat" );	
	}
	
	//Be advised, you have a large concentration of hostiles moving in from the southeast, they just breached the perimeter!	
	radio_dialogue( "est_snp1_hostilesse" );
	
	//I'll try to thin 'em out before they get too close. Recommend you switch to scoped weapons, over.	
	radio_dialogue( "est_snp1_thinemout" );
	
	if( isalive( level.scarecrow ) || isalive( level.ozone ) )
	{
		//Roger that! Everyone cover the field to the southeast! Move!	
		radio_dialogue( "est_gst_fieldtose" );
	}
	
	if( isalive( level.ozone ) )
	{
		//I got eyes on! Here they come! They're in the field to the the southeast!	
		radio_dialogue( "est_ozn_eyeson" );
	}
	
	flag_clear( "strike_package_birchfield_dialogue" );
}

birchfield_leftflank_routing()
{
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	//nodes = getnodearray( "birchfield_smokeassault_1a", "targetname" );
	
	nodeLoiterTime = randomfloatrange( 0.5, 2 );
	nodeInitRadius = 256;
	nodeEndRadius = 128;
	nodeClosureRate = 0.5;
	nodeClosureInterval = 0.5;
	earlyEndonMsg = "player_is_escaping";
	
	nodes = getnodearray( "birchfield_smokeassault_2a", "targetname" );
	
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	self terminal_guidance();	
}

birchfield_rightflank_routing()
{
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	nodeLoiterTime = randomfloatrange( 0.5, 2 );
	nodeInitRadius = 256;
	nodeEndRadius = 128;
	nodeClosureRate = 0.5;
	nodeClosureInterval = 0.5;
	earlyEndonMsg = "player_is_escaping";
	
	nodes = getnodearray( "birchfield_smokeassault_1a", "targetname" );
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	self terminal_guidance();	
}

birchfield_centersupport_routing()
{
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	nodeLoiterTime = randomfloatrange( 2, 4 );
	nodeInitRadius = 2100;
	nodeEndRadius = 1000;
	nodeClosureRate = 0.85;
	nodeClosureInterval = 10;
	earlyEndonMsg = "player_is_escaping";
	
	nodes = getnodearray( "birchfield_smokeassault_3a", "targetname" );
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	self terminal_guidance();	
}

//=================================================

strike_package_boathouse_helidrop()
{
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "strike_package_boathouse_dialogue" );
	
	level.packageThresholdPop = 12;	//max number of AI that is allowed to be alive before deploying this group
	level.packageTime = 45;

	if( !flag( "dsm_recovered" ) )
	{
		thread strike_package_boathouse_helidrop_dialogue();
	}
	
	heli1 = spawn_vehicle_from_targetname_and_drive( "boathouse_md500" );	
	heli2 = spawn_vehicle_from_targetname_and_drive( "boathouse_mi17" );	
	
	thread defense_helidrop_rider_setup( heli1, "boathouse_md500" );
	thread defense_helidrop_rider_setup( heli2, "boathouse_mi17" );
	
	wait 3;
	
	flag_set( "strike_package_spawned" );
}

strike_package_boathouse_helidrop_dialogue()
{
	//flag_wait( "boathouse_invaders_arrived" );
	wait 5;
	
	//They're dropping in more troops west of the house!	
	radio_dialogue( "est_snp1_troopswest" );
	
	//They must be by the boathouse! Cover the west approach! 
	radio_dialogue( "est_gst_boathouse" );
	
	if( isalive( level.ozone ) )
	{	
		//We got 249s and RPGs at the dining room window, plus L86 machine guns.	
		radio_dialogue( "est_ozn_249sandrpgs" );
		
		//Roger that, use 'em to cut 'em down as they come outta the treeline!	
		radio_dialogue( "est_gst_cutemdown" );
	}
	
	flag_clear( "strike_package_boathouse_dialogue" );
}

//=================================================

strike_package_solarfield()
{
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "strike_package_solarfield_dialogue" );
	
	level.packageThresholdPop = 17;	//max number of AI that is allowed to be alive before deploying this group
	level.packageTime = 60;
	
	flag_wait( "activate_package_on_standby" );
	flag_clear( "activate_package_on_standby" );
	
	if( !flag( "dsm_recovered" ) )
	{
		thread strike_package_solarfield_dialogue();
	}
	
	//spawn the attack as one big batch, no respawns
	
	core_spawn( "solarfield_pkg_openground", ::solarfield_routing_openground );
	core_spawn( "solarfield_pkg_forest", ::solarfield_routing_forest );
	
	thread smokescreen( "solarfield_pkg_smokepot" );
	
	wait 3;
	
	flag_set( "strike_package_spawned" );
}

strike_package_solarfield_dialogue()
{
	level endon ( "dsm_recovered" );
	
	//I have eyes on additional hostile forces moving in on your position. They're approaching through the solar panels east of the house.	
	radio_dialogue( "est_snp1_additionalhostile" );
	
	//They're moving in through the solar panels east of the house!	
	radio_dialogue( "est_gst_solarpanelseast" );
		
	if( isalive( level.scarecrow ) )
	{
		//Roger, I'll try to cut 'em off as they come through the trees.	
		radio_dialogue( "est_scr_comethrutrees" );
	}

	claymoreCount = level.player GetWeaponAmmoStock( "claymore" );
		
	if( level.gameSkill < 2 && !flag( "claymore_hint_printed" ) && claymoreCount > 4 )
	{
		//only print a hint on screen for easy and normal and less than 5 claymores in their stockpile
			
		flag_set( "claymore_hint_printed" );
		level.player thread display_hint( "claymore_hint" );
	}
		
	//Use your claymores if you have 'em. Plant 'em around the trail east of the house.	
	radio_dialogue( "est_gst_easttrail" );
	
	flag_clear( "strike_package_solarfield_dialogue" );
}

solarfield_routing_openground()
{
	//these guys are faster and more overt
	//they change locations faster and carry SMGs
	
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	nodeLoiterTime = randomfloatrange( 0.5, 2 );
	nodeInitRadius = 2400;
	nodeEndRadius = 1200;
	nodeClosureRate = 0.8;
	nodeClosureInterval = 0.7;
	earlyEndonMsg = "player_is_escaping";
	
	nodes = getnodearray( "solarfield_pkg_route_1a", "targetname" );
	
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	self terminal_guidance();	
}

solarfield_routing_forest()
{
	//these guys are slower and sneakier
	//they carry heavier weapons
	
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	self enable_cqbwalk();
	
	nodeLoiterTime = randomfloatrange( 0.5, 2 );
	nodeInitRadius = 1000;
	nodeEndRadius = 800;
	nodeClosureRate = 0.9;
	nodeClosureInterval = 5;
	earlyEndonMsg = "player_is_escaping";
	
	nodes = getnodearray( "solarfield_pkg_route_2a", "targetname" );
	
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	wait 10;
	
	self terminal_guidance();	
}


//=================================================

strike_package_frontyard_md500_rush()
{
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "strike_package_md500rush_dialogue" );
	
	//level.packageThresholdPop = 8;	//max number of AI that is allowed to be alive before deploying this group
	level.packageThresholdPop = 2;
	level.packageTime = 90;

	if( !flag( "dsm_recovered" ) )
	{
		thread strike_package_md500_rush_dialogue();
	}
	
	heli1 = spawn_vehicle_from_targetname_and_drive( "md500_rush_1" );	
	heli2 = spawn_vehicle_from_targetname_and_drive( "md500_rush_2" );	
	heli3 = spawn_vehicle_from_targetname_and_drive( "md500_rush_3" );	
	
	thread defense_helidrop_rider_setup( heli1, "md500_rush_1" );
	thread defense_helidrop_rider_setup( heli2, "md500_rush_2" );
	thread defense_helidrop_rider_setup( heli3, "md500_rush_3" );
	
	wait 3;
	
	flag_set( "strike_package_spawned" );
}

strike_package_md500_rush_dialogue()
{
	level endon ( "dsm_recovered" );
	
	//Enemy fast-attack choppers coming in from the northwest.	
	radio_dialogue( "est_snp1_fastattack" );
	
	//Roger that. Enemy helos approaching from the northwest.	
	radio_dialogue( "est_gst_helosnw" );
	
	if( isalive( level.scarecrow ) )
	{	
		//We gotta cover the front lawn! 	
		radio_dialogue( "est_scr_frontlawn" );
	}
	
	if( isalive( level.ozone ) )
	{
		node = getnode( "scarecrow_housebriefing_start", "targetname" );
		level.ozone disable_ai_color();
		level.ozone setgoalnode( node );
		
		//I'm moving to the main windows, I need someone to mine and cover the driveway approach.	
		radio_dialogue( "est_ozn_mainwindows" );
	}
	
	claymoreCount = level.player GetWeaponAmmoStock( "claymore" );
		
	if( level.gameSkill < 2 && !flag( "claymore_hint_printed" ) && claymoreCount > 4 )
	{
		//only print a hint on screen for easy and normal and if they have more than 5 claymores left in stockpile
		
		flag_set( "claymore_hint_printed" );
		level.player thread display_hint( "claymore_hint" );
	}
	
	//Roach, use your claymores on the driveway and pull back to the house!	
	radio_dialogue( "est_gst_useclaymores" );
	
	flag_clear( "strike_package_md500rush_dialogue" );
}

//****************************************//
// STRIKE COMPONENTS - BASIC SMOKEASSAULT
//****************************************//

//****************************************//
// STRIKE COMPONENTS - RPG TEAMS
//****************************************//

strike_component_rpg_team_stables()
{
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "rpg_stables_dialogue" );
	
	flag_set( "strike_component_activated" );
	
	level.componentThresholdPop = 24;	//max number of AI that is allowed to be alive before deploying this component
	
	flag_wait( "activate_component_on_standby" );
	flag_clear( "activate_component_on_standby" );
	
	core_spawn( "stables_rpg_team", ::rpg_team_nav, "stables_rpg_team_fp" );
	
	//RPG team moving in from the east!!	
	radio_dialogue( "est_snp1_rpgteameast" );
	
	if( isalive( level.ozone ) )
	{
		//Roger that, RPG team moving in from the east!!	
		radio_dialogue( "est_ozn_rpgteameast" );
	}
	
	flag_clear( "rpg_stables_dialogue" );
}

strike_component_rpg_team_boathouse()
{
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "rpg_boathouse_dialogue" );
	
	flag_set( "strike_component_activated" );
	
	level.componentThresholdPop = 23;	//max number of AI that is allowed to be alive before deploying this component
	
	flag_wait( "activate_component_on_standby" );
	flag_clear( "activate_component_on_standby" );
	
	core_spawn( "boathouse_rpg_team", ::rpg_team_nav, "boathouse_rpg_team_fp" );
	
	//RPG team approaching from the west!!	
	radio_dialogue( "est_snp1_rpgteamwest" );
	
	//Solid copy! RPG team approaching from the west!!	
	radio_dialogue( "est_gst_rpgteamwest" );
	
	flag_clear( "rpg_boathouse_dialogue" );
}

strike_component_rpg_team_southwest()
{
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );	
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "sniper_breaktime_dialogue" );
	
	flag_set( "rpg_southwest_dialogue" );
	
	flag_set( "strike_component_activated" );
	
	level.componentThresholdPop = 23;	//max number of AI that is allowed to be alive before deploying this component
	
	flag_wait( "activate_component_on_standby" );
	flag_clear( "activate_component_on_standby" );
	
	core_spawn( "southwest_rpg_team", ::rpg_team_nav, "southwest_rpg_team_fp" );
	
	//RPG team moving in from the southwest!!	
	radio_dialogue( "est_snp1_rpgteamsw" );
	
	if( isalive( level.ozone ) )
	{
		//Got it! RPG team moving in from the southwest!!	
		radio_dialogue( "est_ozn_rpgteamsw" );
	}
	
	flag_clear( "rpg_southwest_dialogue" );
}

rpg_team_nav( nodeName )
{
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon ( "death" );
	
	nodes = getnodearray( nodeName, "targetname" );
	assertEX( nodes.size, "You have to place some nodes with the targetname " + nodeName );

	node = nodes[ randomint( nodes.size ) ];
	
	nodeLoiterTime = 2.5;
	nodeInitRadius = 2400;
	nodeEndRadius = 200;
	nodeClosureRate = 0.5;
	nodeClosureInterval = randomfloatrange( 0.5, 2  );
	earlyEndonMsg = "player_is_escaping";
	
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
		self.goalradius = 2200;
		wait randomfloatrange( 25, 30 );
	}
	
	self terminal_guidance();	
}

//****************************************//
// HELICOPTER AI SPAWN
//****************************************//

defense_helidrop_rider_setup( heli, heliName )
{
	//wait 0.05;	//*TEMP* take this out when the heli riders bug is fixed
	riders = heli.riders;
	
	nodes = getnodearray( heliName, "targetname" );
	
	assertEX( nodes.size, "You have to have at least one node for the riders to attack towards, with the same targetname as the helicopter they're from." );
	
	startNode = nodes[ randomint( nodes.size ) ];

	//use targetnames on nodes tied to helicopters
	//one or more appropriate node chains lead to the DSM for a given helicopter
	//randomly pick one for the whole group so they stick together, send them on an attack mission
	
	foreach( rider in riders )
	{
		if( isdefined( rider.script_startingposition ) )
		{
			if( isdefined( heli.vehicletype ) )
			{
				if( heli.vehicletype == "littlebird" )
				{
					if( rider.script_startingposition == 1 )
					{
						continue;
					}
				}
				
				if( heli.vehicletype == "mi17" )
				{
					if( rider.script_startingposition == 0 )
					{
						continue;
					}
				}
			}
		}
		
		if( isalive( rider ) )
		{
			rider thread defense_helidrop_rider_deploy( heli, heliName, startNode );
		}
	}
	
}

defense_helidrop_rider_deploy( heli, heliName, startNode )
{
	assertEX( isalive( self ), "Tried to pass a dead AI into this function." );
	
	self endon( "death" );

	if ( isdefined( heli ) )
		heli waittill( "unloaded" );

	//access thread for customsettings

	settings = defense_helidrop_rider_settings( heliName );

	self.goalradius = settings[ "goalradius" ];
	
	node = startNode;
	
	nodeLoiterTime = settings[ "nodeLoiterTime" ];
	nodeInitRadius = settings[ "nodeInitRadius" ];
	nodeEndRadius = settings[ "nodeEndRadius" ];
	nodeClosureRate = settings[ "nodeClosureRate" ];
	nodeClosureInterval = randomfloatrange( settings[ "nodeClosureIntervalLow" ], settings[ "nodeClosureIntervalHigh" ] );
	
	earlyEndonMsg = "player_is_escaping";
	
	if( !flag( "player_is_escaping" ) )
	{
		self roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg );
	}
	
	self terminal_guidance();
}

defense_helidrop_rider_settings( heliName )
{
	//filter by helicopter targetname
	
	settings = [];
	
	if( heliName == "md500_rush_1" )
	{
		settings[ "goalradius" ] = 3000;

		settings[ "nodeLoiterTime" ] = 8;
		settings[ "nodeInitRadius" ] = 3000;
		settings[ "nodeEndRadius" ] = 1400;
		settings[ "nodeClosureRate" ] = 0.75;
		settings[ "nodeClosureIntervalLow" ] = 3;
		settings[ "nodeClosureIntervalHigh" ] = 8;
	}
	if( heliName == "md500_rush_2" )
	{
		settings[ "goalradius" ] = 2000;
	
		settings[ "nodeLoiterTime" ] = 3;
		settings[ "nodeInitRadius" ] = 1600;
		settings[ "nodeEndRadius" ] = 800;
		settings[ "nodeClosureRate" ] = 0.5;
		settings[ "nodeClosureIntervalLow" ] = 8;
		settings[ "nodeClosureIntervalHigh" ] = 12;
	}
	if( heliName == "boathouse_md500" )
	{
		settings[ "goalradius" ] = 1600;
	
		settings[ "nodeLoiterTime" ] = 4;
		settings[ "nodeInitRadius" ] = 1600;
		settings[ "nodeEndRadius" ] = 1000;
		settings[ "nodeClosureRate" ] = 0.85;
		settings[ "nodeClosureIntervalLow" ] = 3;
		settings[ "nodeClosureIntervalHigh" ] = 6;
	}
	if( heliName == "boathouse_mi17" )
	{
		settings[ "goalradius" ] = 1800;
	
		settings[ "nodeLoiterTime" ] = 4;
		settings[ "nodeInitRadius" ] = 1800;
		settings[ "nodeEndRadius" ] = 1400;
		settings[ "nodeClosureRate" ] = 0.9;
		settings[ "nodeClosureIntervalLow" ] = 4;
		settings[ "nodeClosureIntervalHigh" ] = 6;
	}
	else
	{
		settings[ "goalradius" ] = 3000;
	
		settings[ "nodeLoiterTime" ] = 0;
		settings[ "nodeInitRadius" ] = 3000;
		settings[ "nodeEndRadius" ] = 1000;
		settings[ "nodeClosureRate" ] = 0.8;
		settings[ "nodeClosureIntervalLow" ] = 3;
		settings[ "nodeClosureIntervalHigh" ] = 7;
	}
	
	assertEX( settings.size == 7, "Missing one or more settings for one of the helidrop AI." );
	return( settings );	
}

//****************************************//
// AI SPAWN & BIOMETRICS
//****************************************//

core_spawn( spawnerName, spawnFunction, var )
{
	if ( !isdefined( var ) )
	{
		array_spawn_function_targetname( spawnerName, spawnFunction );
	}
	else
	{
		array_spawn_function_targetname( spawnerName, spawnFunction, var );
	}
	
	spawners = [];
    spawners = getentarray( spawnerName, "targetname" );
    array_thread( spawners, ::spawn_ai );  
}

defense_enemy_spawn()
{
	//this is run on all enemies that spawn after the download is started for enemy pop tracking
	
	array_spawn_function_noteworthy( "counterattacker", ::defense_enemy_init );
	array_spawn_function_noteworthy( "counterattacker", ::defense_enemy_flashbang_tweak );
	
}

defense_enemy_flashbang_tweak()
{
	if ( level.gameSkill >= 2 )
	{
		// hard and veteran
		self.doorFlashChance = 1;
	}
	else
	{
		// normal and easy
		self.doorFlashChance = .7;
	}
}

defense_enemy_init()
{
	self endon ( "death" );
	self thread defense_enemy_deathmonitor();
	level.enemyTotalPop++;
}

defense_enemy_deathmonitor()
{
	self waittill ( "death" );
	level notify ( "counterattacker_died" );
	level.enemyTotalPop--;
}

//****************************************//
// ENDING AI DRONES
//****************************************//

ending_shadowops_dronespawn()
{
	array_spawn_function_noteworthy( "ending_shadowops_drone", ::ending_shadowops_drone_init );
}

ending_shadowops_drone_init()
{
	self.team = "allies";
	self.pathRandomPercent = randomintrange( 0, 100 );
	self enable_cqbwalk();
	
	self waittill ( "goal" );
	self allowedstances( "crouch" );
}

//****************************************//
// AI NAVIGATION & ENCROACHMENT
//****************************************//

roaming_nodechain_nav( node, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg )
{
	//node 				= node to start from
	//nodeLoiterTime	= how long to stay at the current node post-closure phase, before being assigned to the next one
	//nodeEndRadius		= smallest goalradius around the node to reach before loitering
	//nodeClosureRate	= percentage of radius reduction per nodeClosureInterval to pull the AI closer to the node
	//nodeClosureInterval = time to wait before reducing the goalradius
	//earlyEndonMsg		= msg to endon
	
	self endon ( "death" );
	
	if ( isdefined( earlyEndonMsg ) )
	{
		level endon( earlyEndonMsg );
	}
	
	currentRadius = nodeInitRadius;
	
	while( 1 )
	{
		self setgoalnode( node );
		self waittill ( "goal" );
		
		while( currentRadius > nodeEndRadius )
		{
			currentRadius = currentRadius * nodeClosureRate;
			self.goalradius = currentRadius;
			self.goalheight = 32;
			self.pathRandomPercent = randomintrange( 0, 100 );
			self waittill ( "goal" );
			wait nodeClosureInterval;
		}
		
		wait nodeLoiterTime;
	
		if( isdefined( node.target ) )
			node = getnode( node.target, "targetname" );
		else
			break;
	}
}

roaming_nodecluster_nav( nodes, nodeLoiterTime, nodeInitRadius, nodeEndRadius, nodeClosureRate, nodeClosureInterval, earlyEndonMsg )
{
	//nodes				= array of potential starting nodes
	//nodeLoiterTime	= how long to stay at the current node post-closure phase, before being assigned to the next one
	//nodeEndRadius		= smallest goalradius around the node to reach before loitering
	//nodeClosureRate	= percentage of radius reduction per nodeClosureInterval to pull the AI closer to the node
	//nodeClosureInterval = time to wait before reducing the goalradius
	//earlyEndonMsg		= msg to endon
	
	//setup in map: make a set of starting nodes
		//draw an imaginary line to the end destination from the general starting area
		//set up multiple nodes at significantly different positions as 'clusters', 
		//each cluster is one side of the line only, clusters sequentially alternate sides of the line
	
	//randomly pick one of the starting nodes
	//use nodecluster encroachment loop with adjustable closure rate settings
	
	//randomly pick one node from the next 'cluster'
	//use nodecluster encroachment loop with adjustable closure rate settings
	//loop until no more clusters exist
	
	self endon ( "death" );
	
	if ( isdefined( earlyEndonMsg ) )
	{
		level endon( earlyEndonMsg );
	}
	
	currentRadius = nodeInitRadius;
	
	assertEX( isdefined( nodes ), "Must pass an array of starting nodes." );
	node = nodes[ randomint( nodes.size ) ];
	
	while( 1 )
	{	
		self setgoalnode( node );
		self waittill ( "goal" );
		
		while( currentRadius > nodeEndRadius )
		{
			currentRadius = currentRadius * nodeClosureRate;
			self.goalradius = currentRadius;
			self.goalheight = 32;
			self.pathRandomPercent = randomintrange( 0, 100 );
			self waittill ( "goal" );
			wait nodeClosureInterval;
		}
		
		wait nodeLoiterTime;
	
		if( isdefined( node.script_noteworthy ) )
		{
			nodes = getnodearray( node.script_noteworthy, "targetname" );
			assertEX( isdefined( nodes.size ), "Can't find any nodes with targetname " + node.script_noteworthy );
			
			node = nodes[ randomint( nodes.size ) ];
		}
		else
		{
			break;
		}
	}
}

terminal_guidance()
{
	//attack the DSM
	//attack the player using default AI if there is no DSM to attack
	//intercept the player on a new navigation goal if player_is_escaping is set
	
	self endon( "death" );
	
	//Start monitoring the trigger for failsafe suicide bombing if touched by this enemy
	//Do not start dsm destruction if the player has recovered the DSM && has hit the player_is_escaping trigger
	//However, navigation to the DSM is ok even if the DSM has been retrieved if player has not 'escaped' yet
	
	if( !flag( "player_is_escaping" ) )
	{	
		dsm = getent( "dsm", "targetname" );
		
		dsmKillNode = getnode( "dsm_killnode", "targetname" );
		self setgoalnode( dsmKillNode );
		self.goalradius = 4000;
		
		if( !flag( "dsm_recovered" ) && !flag( "dsm_destroyed" ) )
		{
			//self thread dsm_destruction_ai_trig( dsm );
			
			//Now they are made aware of the dsm as a sentient enemy if they haven't seen it already
			
			self getenemyinfo( dsm );	
		}
	}
	
	flag_wait( "player_is_escaping" );
	
	{
		self thread terminal_guidance_playerchase();
	}
}

terminal_guidance_playerchase()
{
	//AI nav once player is escaping with the DSM
	//NOW ATTACK THE PLAYER'S EXFIL ROUTE AND GO AFTER THE PLAYER MORE
	//CHANGE THE ai pursuit SETTINGS IN THE MASTER 'player is escaping' monitoring thread
	
	self endon ( "death" );
	
	chasenode = getnode( "chase_player", "targetname" );
	self setgoalnode( chasenode );
	self.goalradius = 3250;
	self.pathRandomPercent = randomintrange( 30, 100 );
	
	thread set_group_advance_to_enemy_parameters( 5, 1 );
}

terminal_hunters()
{
	flag_wait( "player_is_escaping" );
	
	exfil_hunters = getentarray( "player_exfil_hunter", "targetname" );
    array_thread( exfil_hunters, ::spawn_ai );
}

terminal_blockers()
{
	flag_wait( "point_of_no_return" );
	
	//The enemies that spawn here are too close to the ending dragging sequence, so don't spawn if testing that start point
	
	if( !flag( "test_ending" ) )
	{
		exfil_blockers = getentarray( "player_exfil_blocker", "targetname" );
    	array_thread( exfil_blockers, ::spawn_ai );
    }
}

terminal_hillchasers()
{
	flag_wait( "point_of_no_return" );
	
	trig = getent( "hillchaser_trigger", "targetname" );
	trig waittill ( "trigger" );
	
	exfil_hillchasers = getentarray( "player_exfil_hillchaser", "targetname" );
    array_thread( exfil_hillchasers, ::spawn_ai );
}

//****************************************//
// FRIENDLIES
//****************************************//

tough_friendly_biometrics( guy )
{
	self waittill ( "death" );
	
	assertEX( isdefined( guy ), "Must specify a string that is the name of the friendly." );
	
	if( guy == "scarecrow" )
	{
		//death notification dialogue for scarecrow
	}
	
	if( guy == "ozone" )
	{
		//death notification dialogue for ozone
	}
	
}

tough_friendly_kill()
{
	//TODO wait on a flag before killing them
	//TODO failsafe kill outright during field run from mortar hits!!
}

magic_sniper_guy()
{
	//Once in a while, our magic sniper guy Archer will kill an enemy for the player
	//Occasionally he will go out of contact and say so
	
	flag_wait( "defense_battle_begins" );
	
	flag_set( "sniper_in_position" );
	
	level endon ( "player_is_escaping" );		
	
	shot = false;
	
	while( !flag( "dsm_recovered" ) )
	{
		if( flag( "sniper_in_position" ) )	
		{					
			shot = magic_sniper_guy_targeting();
			wait randomfloatrange( 1, 3 );
			
			if( shot )
			{
				wait 2;
				shot = false;
			}
		}
		else
		{
			flag_wait( "sniper_in_position" );
		}
	}
} 

magic_sniper_guy_breaktimes()
{
	flag_wait( "defense_battle_begins" );
	
	level endon ( "player_is_escaping" );
	
	level waittill ( "magic_sniper_breaktime" );
	
	flag_waitopen( "strike_package_birchfield_dialogue" );
	flag_waitopen( "strike_package_bighelidrop_dialogue" );
	flag_waitopen( "strike_package_boathouse_dialogue" );
	flag_waitopen( "strike_package_solarfield_dialogue" );
	flag_waitopen( "strike_package_md500rush_dialogue" );
	flag_waitopen( "rpg_stables_dialogue" );
	flag_waitopen( "rpg_boathouse_dialogue" );
	flag_waitopen( "rpg_southwest_dialogue" );
	flag_waitopen( "ozone_death_dialogue" );
	flag_waitopen( "scarecrow_death_dialogue" );

	flag_set( "sniper_breaktime_dialogue" );

	if( flag( "sniper_attempting_shot" ) )
	{
		flag_waitopen( "sniper_attempting_shot" );
	}
	
	flag_clear( "sniper_in_position" );

	//I'm displacing. You're gonna be without sniper support for thirty seconds, standby.	
	radio_dialogue( "est_snp1_displacing" );
	
	flag_clear( "sniper_breaktime_dialogue" );

	wait 30;
	
	flag_set( "sniper_in_position" );
}

magic_sniper_guy_targeting( enemies )
{
	level endon ( "player_is_escaping" );
	
	killmsgs = [];
	
	killmsgs[ 0 ] = "est_snp1_tangodown"; 		
	killmsgs[ 1 ] = "est_snp1_gotone"; 			
	killmsgs[ 2 ] = "est_snp1_hostneut"; 		
	killmsgs[ 3 ] = "est_snp1_thatsakill"; 		
	killmsgs[ 4 ] = "est_snp1_thatsone"; 		
	killmsgs[ 5 ] = "est_snp1_tangodown2"; 		
	killmsgs[ 6 ] = "est_snp1_droppedhim"; 		
	killmsgs[ 7 ] = "est_snp1_hesdown"; 
	
	enemies = getaiarray( "axis" );
	
	foreach( enemy in enemies )
	{
		wait 1;
		
		if( isalive( enemy ) )
		{
			linesight = SightTracePassed( level.player.origin + ( 0, 0, 64 ), enemy.origin + ( 0, 0, 32 ), false, undefined );
			//if( linesight )
			
			//trace1 = player_can_see_ai( enemy );
			trace2 = enemy CanSee( level.player );
			dist = length( level.player.origin - enemy.origin );
			dangerCloseSniperRange = 480;	//so it's unlikely to happen in tight indoor spaces
			
			//if( enemy CanSee( level.player ) )
			//if( trace1 && trace2 && dist >= 480 )
			
			if( flag( "sniper_in_position" ) )
			{
				if( linesight && trace2 && dist >= 480 )
				{	
					flag_set( "sniper_attempting_shot" );	
					rnd = randomintrange( 0, 100 );
					if( rnd > 10 )
					{
						enemy thread play_sound_on_entity( "weap_cheytac_fire_plr" );
						enemy kill();
						wait 0.5;
						radio_dialogue( killmsgs[ randomint( killmsgs.size ) ] );
						flag_clear( "sniper_attempting_shot" );	
						return( true );
					}
					else
					{
						enemy play_sound_on_entity( "weap_cheytac_fire_plr" );
						flag_clear( "sniper_attempting_shot" );	
						return( false );
					}
				}
			}
		}
	}
	
	return( false );
}

//****************************************//
// DSM
//****************************************//

dsm_setup()
{
	flag_wait( "download_started" );
	
	//This struct controls the function stack for the ai that queue up to suicide bomb the dsm
	
	level.dsmDestructionController = spawnstruct();
	
	//This makes the dsm look like an AI to the enemy
	//They will seek cover in the goalradius at their engagementdist if no enemy
	//If they have an enemy, they will seek cover within the goalradius to attack the enemy
	//The dsm has a higher threatbias (defaults to 0 on AI) so they will prioritize hunting for it first
	//They will start engaging it at a range of maxvisibledist
	
	dsm = getent( "dsm", "targetname" );
	
	dsm MakeEntitySentient( "allies" );
	dsm.threatbias = 50;
	dsm.maxvisibledist = 300;
	
	thread set_group_advance_to_enemy_parameters( 6, 2 );
	
	flag_wait( "dsm_recovered" );
	
	dsm FreeEntitySentient();
}

dsm_display_control()
{
	dsm_real = getent( "dsm", "targetname" );
	dsm_obj = getent( "dsm_obj", "targetname" );
		
	dsm_real hide();
	dsm_obj hide();
		
	flag_wait( "dsm_ready_to_use" );
	
	trig = getent( "dsm_usetrigger", "targetname" );
	trig sethintstring( &"ESTATE_DSM_USE_HINT" );
	
	dsm_obj show();
	
	flag_wait( "download_started" );
	flag_set( "dsm_exposed" );
	
	autosave_by_name( "started_download" );
	
	dsm_obj hide();
	dsm_real show();
	
	dsm_real thread dsm_sounds();
	
	flag_clear( "dsm_ready_to_use" );
	
	trig sethintstring( &"ESTATE_DSM_PICKUP_HINT" );
	
	flag_wait( "download_complete" );
	flag_set( "dsm_ready_to_use" );
	
	dsm_obj show();
	
	trig waittill ( "trigger" );
	
	level.player thread play_sound_on_entity( "dsm_pickup" );
	
	if( !flag( "fence_removed" ) )
	{
		fence = getent( "final_area_fence", "targetname" ); 
		fence delete();
		flag_set( "fence_removed" );
	}
	
	flag_clear( "dsm_ready_to_use" );
	flag_clear( "dsm_exposed" );
	
	dsm_obj delete();
	dsm_real hide();
	
	flag_set( "dsm_recovered" );
	
	if( !flag( "can_save" ) )
	{
		flag_set( "can_save" );
	}
}

dsm_sounds()
{
	self playsound( "scn_estate_data_grab_setdown" );
	
	wait 2;
	
	self playloopsound( "scn_estate_data_grab_loop" );
	
	flag_wait( "download_complete" );
	
	self stoploopsound();
}

download_progress()
{
	flag_wait( "download_started" );
	
	level endon ( "main_defense_fight_finished" );
	level endon ( "player_is_escaping" );
	level endon ( "dsm_has_been_destroyed" );
	
	if( !flag( "download_test" ) )
	{
		level.hudelem = maps\_hud_util::get_countdown_hud( -300, undefined, undefined, true );
		level.hudelem SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
		level.hudelem.label = &"ESTATE_DSM_FRAME";	// DSM v6.04
		//level.hudelem.fontScale = 1.5;
		wait 0.65;
		
		level.hudelem_status = maps\_hud_util::get_countdown_hud( -200, undefined, undefined, true );
		level.hudelem_status SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
		level.hudelem_status.label = &"ESTATE_DSM_WORKING"; // ...working...
		wait 2.85;
		
		level.hudelem_status destroy();
		level.hudelem_status = maps\_hud_util::get_countdown_hud( -200, undefined, undefined, true );
		level.hudelem_status SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
		level.hudelem_status.label = &"ESTATE_DSM_NETWORK_FOUND"; // ...network found...
		wait 3.75;
		
		level.hudelem_status destroy();
		level.hudelem_status = maps\_hud_util::get_countdown_hud( -200, undefined, undefined, true );
		level.hudelem_status SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
		level.hudelem_status.label = &"ESTATE_DSM_IRONBOX"; // ...ironbox detected...
		wait 2.25; 
		
		level.hudelem_status destroy();
		
		level.hudelem_status = maps\_hud_util::get_countdown_hud( -200, undefined, undefined, true );
		level.hudelem_status SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
		level.hudelem_status.label = &"ESTATE_DSM_BYPASS"; // ...bypassed.
		wait 3.1;
		
		level.hudelem destroy();
		level.hudelem_status destroy();	
	}
	
	//Download meter - internally fixed time, visibly variable rate update, hard coded

	//6 minutes of defense battle gameplay

	//58 files at 1 file per second = 58 s
	//122 files at 2 files per second = 61 s
	//244 files at 4 files per second = 61 s
	//600 files at 10 files per second = 60 s
	//2400 files at 20 files per second max rate = 120s

	//3424 files over 360s (6 mins)
	
	//medium version
	
	//5 minutes of defense battle gameplay

	//88 files at 1 file per second = 88 s
	//122 files at 2 files per second = 61 s
	//204 files at 4 files per second = 51 s
	//600 files at 10 files per second = 60 s
	//800 files at 20 files per second max rate = 40s

	//1814 files over 300s (5 mins)
	
	//short version
	
	//4 minutes of defense battle gameplay

	//58 files at 1 file per second = 58 s
	//62 files at 2 files per second = 31 s
	//124 files at 4 files per second = 31 s
	//300 files at 10 files per second = 30 s
	//600 files at 20 files per second max rate = 30s

	//1144 files over 180s (3 mins)
	
	//subtract from available time pool
	//only increment by 1 second, cycle through the rates on a per second basis up and down, 12345, 54321 = marker
	
	level.currentfiles = 0;
	
	//8 minutes
	
	/*
	level.downloadGroups = [];
	level.downloadGroups[ 1 ] = 58;		//1
	level.downloadGroups[ 2 ] = 2400;	//20 fps
	level.downloadGroups[ 3 ] = 122;	//2 fps
	level.downloadGroups[ 4 ] = 244;	//4 fps
	level.downloadGroups[ 5 ] = 600;	//10 fps
	*/
	
	//5 minutes
	
	level.downloadGroups = [];
	level.downloadGroups[ 1 ] = 95;		//1 fps		//5 files per 5 second burst //100 files = 100 seconds
	level.downloadGroups[ 2 ] = 1280;	//20 fps 	//80 files per 4 second burst //1280 files = 64 seconds
	level.downloadGroups[ 3 ] = 112;	//2 fps		//8 files per 4 second burst //112 files = 56 seconds
	level.downloadGroups[ 4 ] = 180;	//4 fps		//12 files per 3 second burst //180 files = 45 seconds
	level.downloadGroups[ 5 ] = 400;	//10 fps 	//50 files per 5 second burst //350 files = 35 seconds
	
	//For testing timer and file meter
	/*
	level.downloadGroups = [];
	level.downloadGroups[ 1 ] = 35;	//1 fps	//5 files per 5 second burst //100 files = 100 seconds
	level.downloadGroups[ 2 ] = 560;	//20 fps //80 files per 4 second burst //1200 files = 60 seconds
	level.downloadGroups[ 3 ] = 56;	//2 fps	//8 files seconds per 4 second burst //120 files = 60 seconds
	level.downloadGroups[ 4 ] = 84;	//4 fps	//12 files seconds per 3 second burst //180 files = 45 seconds
	level.downloadGroups[ 5 ] = 350;	//10 fps //50 files per 5 second burst //350 files = 35 seconds
	*/
	
	//3 minutes
	
	/*
	level.downloadGroups = [];
	level.downloadGroups[ 1 ] = 58;		//1 fps
	level.downloadGroups[ 2 ] = 600;	//20 fps
	level.downloadGroups[ 3 ] = 62;		//2 fps
	level.downloadGroups[ 4 ] = 124;	//4 fps
	level.downloadGroups[ 5 ] = 300;	//10 fps
	*/
	
	level.totalfiles = 0;
	
	for( i = 1; i <= 5; i++ )
	{
		level.totalfiles = level.totalfiles + level.downloadGroups[ i ];
	}
	
	level.hudelem = maps\_hud_util::get_countdown_hud( -210 );	//205
	level.hudelem SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem.label = &"ESTATE_DSM_PROGRESS"; // Files copied:
	
	level.hudelem_status = maps\_hud_util::get_download_state_hud( -62, undefined, undefined, true );
	level.hudelem_status SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_status setvalue( 0 );
	
	level.hudelem_status_total = maps\_hud_util::get_countdown_hud( -62, undefined, undefined, true );
	level.hudelem_status_total SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	
	//level.hudelem_status_total.label = "/" + level.totalfiles;
	level.hudelem_status_total.label = &"ESTATE_DSM_SLASH_TOTALFILES";
	
	level.hudelem_dltimer_heading = maps\_hud_util::get_countdown_hud( -210, 120 );
	level.hudelem_dltimer_heading SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dltimer_heading.label = &"ESTATE_DSM_DLTIMELEFT";	// Time left:
	level.hudelem_dltimer_heading.fontScale = 1.1;
	level.hudelem_dltimer_heading.color = ( 0.4, 0.5, 0.4 );
	
	level.hudelem_dltimer_value = maps\_hud_util::get_countdown_hud( -132, 120, undefined, true );
	level.hudelem_dltimer_value SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dltimer_value.fontScale = 1.1;
	level.hudelem_dltimer_value.color = ( 0.4, 0.5, 0.4 );
	level.hudelem_dltimer_value.alignX = "right";
	
	level.hudelem_dltimer_units = maps\_hud_util::get_countdown_hud( -126, 120, undefined, true );
	level.hudelem_dltimer_units SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dltimer_units.label = &"ESTATE_DSM_DLTIMELEFT_MINS";	// mins
	level.hudelem_dltimer_units.fontScale = 1.1;
	level.hudelem_dltimer_units.color = ( 0.4, 0.5, 0.4 );
	
	level.hudelem_dlrate_heading = maps\_hud_util::get_countdown_hud( -91, 120, undefined, true );
	level.hudelem_dlrate_heading SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dlrate_heading.label = &"ESTATE_DSM_DL_RATEMETER";	// at
	level.hudelem_dlrate_heading.fontScale = 1.1;
	level.hudelem_dlrate_heading.color = ( 0.4, 0.5, 0.4 );
	
	level.hudelem_dlrate_value = maps\_hud_util::get_countdown_hud( -44, 120, undefined, true );
	level.hudelem_dlrate_value SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dlrate_value.fontScale = 1.1;
	level.hudelem_dlrate_value.color = ( 0.4, 0.5, 0.4 );
	level.hudelem_dlrate_value.alignX = "right";
	
	level.hudelem_dlrate_units = maps\_hud_util::get_countdown_hud( -41, 120, undefined, true );
	level.hudelem_dlrate_units SetPulseFX( 30, 900000, 700 );// something, decay start, decay duration
	level.hudelem_dlrate_units.label = &"ESTATE_DSM_DLRATE";	// Mbps
	level.hudelem_dlrate_units.fontScale = 1.1;
	level.hudelem_dlrate_units.color = ( 0.4, 0.5, 0.4 );
	
	level.switchtosecs = 0.85;
	
	flag_set( "download_data_initialized" );
	
	marker = undefined;
	rateTime = undefined;
	segment = undefined;
	
	startTime = gettime();
	
 	while( level.currentfiles < level.totalfiles )
 	{
 		/*
 		if( level.currentfiles > 1200 )
 		{
 			thread ending_moments();
 			badguys = getaiarray( "axis" );
 			foreach( guy in badguys )
 			{
 				guy kill();
 			}
 			break;
 		}
 		*/	
 				
 		level.timeElapsed = ( gettime() - startTime ) / 1000;
 		
 		fileSegment = 0;		
 		
 		//Select a download rate
 		
 		num = randomintrange( 1, 100 );
 		
 		if( num > 0 && num < 10 ) 
 		{
 			marker = 5;
 		}
 		
 		if( num >= 10 && num < 25 ) 
 		{
 			marker = 2;
 		}
 		
 		if( num >= 25 && num < 50 ) 
 		{
 			marker = 1;
 		}
 		
 		if( num >= 50 && num < 75 )
 		{
 			marker = 3;
 		}
 		
 		if( num >= 75 && num < 100 )
 		{
 			marker = 4;
 		}
 		 
 		switch( marker )
 		{
 			case 1:
 				//segment = 1;
 				segment = 5;
 				rateTime = 1;		//1 fps	//5 seconds per burst
 				break;
 			case 2:
 				//segment = 20;
 				segment = 80;
 				rateTime = 1 / 20;	//20 fps //4 seconds per burst
 				break;
 			case 3:
 				//segment = 2;
 				segment = 8;
 				rateTime = 1 / 2;	//2 fps	//4 seconds per burst
 				break;
 			case 4:
 				//segment = 4;
 				segment = 12;
 				rateTime = 1 / 4;	//4 fps	//3 seconds per burst
 				break;
 			case 5:
 				//segment = 10;
 				segment = 50;
 				rateTime = 1 / 10; 	//10 fps //5 seconds per burst
 				break;
 		}
 		
 		download_update( fileSegment, segment, rateTime, marker );

		if( marker == 5 )
 		{
 			marker = 1;
 		}
 		else
 		{
 			marker++;
 		}
 	}
	
	flag_set( "download_complete" );
	
	if( !flag( "can_save" ) )
		flag_set( "can_save" );
	
	thread download_display_delete();
}

download_display_delete()
{
	if ( isdefined( level.hudelem ) )
		level.hudelem destroy();
	
	if ( isdefined( level.hudelem_status ) )
		level.hudelem_status destroy();
	
	if ( isdefined( level.hudelem_status_total ) )
		level.hudelem_status_total destroy();
	
	//TIMER
		
	if ( isdefined( level.hudelem_dltimer ) )
		level.hudelem_dltimer destroy();
		
	if ( isdefined( level.hudelem_dltimer_value ) )
		level.hudelem_dltimer_value destroy();
	
	if ( isdefined( level.hudelem_dltimer_heading ) )
		level.hudelem_dltimer_heading destroy();
	
	if ( isdefined( level.hudelem_dltimer_secs ) )
		level.hudelem_dltimer_secs destroy();
	
	//DATA RATE

	if ( isdefined( level.hudelem_dlrate_heading ) )
		level.hudelem_dlrate_heading destroy();
		
	if ( isdefined( level.hudelem_dltimer_units ) )
		level.hudelem_dltimer_units destroy();
		
	if ( isdefined( level.hudelem_dlrate_value ) )
		level.hudelem_dlrate_value destroy();
	
	if ( isdefined( level.hudelem_dlrate_units ) )
		level.hudelem_dlrate_units destroy();	
}

download_update( fileSegment, segment, rateTime, marker )
{
	level endon ( "dsm_has_been_destroyed" );
	
	if( !level.downloadGroups[ marker ] )
		return;
		
	//set the download Mbps rate to dovetail the change in file acquisition speed
	//make the time remaining based off a constant known data size	
	
	//300 seconds at 20fps is 6000 files, top sustained speed of 26.8 Mbps
	
	//300 seconds at 20 fps (5 minutes) +/- 2 minutes, 26.8 +/- 6 Mbps
	//6000 seconds at 1 fps (100 minutes) +/- 30 minutes, 0.13 +/- 0.05 Mbps
	//3000 seconds at 2 fps (50 minutes) +/- 15 minutes, 0.3 +/- 0.16 Mbps
	//1500 seconds at 4 fps (25 minutes) +/- 8 minutes, 5 +/- 2.2 Mbps
	//600 seconds at 10 fps (10 minutes) +/- 3 minutes, 14 +/- 3.7 Mbps
	
	dlrate = undefined;
	timeleft = undefined;
	
	switch( marker )
	{
		case 1:	//1 fps
			timeleft = randomintrange( 70, 130 );
			dlrate = randomfloatrange( 0.08, 0.18 );
			break;
		case 2: //20 fps
			timeleft = randomintrange( 3, 5 );
			dlrate = randomfloatrange( 20.8, 32.8 );
			break;
		case 3: //2 fps
			timeleft = randomintrange( 35, 65 );
			dlrate = randomfloatrange( 0.17, 0.46 );
			break;
		case 4: //4 fps
			timeleft = randomintrange( 17, 33 );
			dlrate = randomfloatrange( 2.8, 7.2 );
			break;
		case 5: //10 fps
			timeleft = randomintrange( 7, 13 );
			dlrate = randomfloatrange( 11.7, 17.7 );
			break;
	}
	
	dlrate = digi_rounder( dlrate );
	
	frac = level.currentfiles / level.totalfiles;
	
	if( frac < level.switchtosecs )
	{
		level.hudelem_dltimer_value setvalue( timeleft );	
	}
	
	level.hudelem_dlrate_value SetValue( dlrate ) ;
		
	while( fileSegment < segment )
	{
		fileSegment++;
		
		level.currentfiles++;
		level.hudelem_status Setvalue( level.currentfiles );
		
		dsm_real = getent( "dsm", "targetname" );
		playerDSMdist = length( level.player.origin - dsm_real.origin );
		
		//Save only if player is within reasonable range of DSM 
		//&& DSM is at 70% integrity or greater 
		//&& not in the basement areas (a bit of anti-camping)
		
		badsave_volume = getent( "no_autosave_in_basement", "targetname" );
		
		safeDSMhealth = level.dsmHealthMax * 0.7;
		
		if( playerDSMdist <= level.playerDSMsafedist && level.dsmHealth >= safeDSMhealth )
		{
			if( !( level.player istouching( badsave_volume ) ) )
			{
				if( level.currentfiles == 300 )
				{
					autosave_by_name( "saved_during_download" );
				}
		 		
		 		if( level.currentfiles == 600 )
				{
					autosave_by_name( "saved_during_download" );
				}
				
				if( level.currentfiles == 900 )
				{
					autosave_by_name( "saved_during_download" );
				}
		 		
		 		if( level.currentfiles == 1200 )
				{
					autosave_by_name( "saved_during_download" );
				}
				
				if( level.currentfiles == 1500 )
				{
					autosave_by_name( "saved_during_download" );
				}
			}
		}
		
		if( level.currentfiles == 1000 )
 		{
 			level notify ( "magic_sniper_breaktime" );
 		}	

		level.downloadGroups[ marker ]--;
		
		wait rateTime;
	}
}

download_fake_timer()
{
	level endon ( "dsm_has_been_destroyed" );
	
	flag_wait( "download_data_initialized" );
	
	timeleft = 0;
	erase_minutes = 0;
	
	while( !flag( "download_complete" ) )
	{
		frac = level.currentfiles / level.totalfiles;
		
		if( frac >= level.switchtosecs && frac < 1 && !erase_minutes )
		{
			erase_minutes = true;
			level.hudelem_dltimer_units.label = &"ESTATE_DSM_DLTIMELEFT_SECS";	// secs
		}
		
		if( frac >= level.switchtosecs && frac < 1 )
		{		
			time1 = level.downloadGroups[ 1 ] / 1;
			time2 = level.downloadGroups[ 2 ] / 20;
			time3 = level.downloadGroups[ 3 ] / 2;
			time4 = level.downloadGroups[ 4 ] / 4;
			time5 = level.downloadGroups[ 5 ] / 10;
			
			timeleft = time1 + time2 + time3 + time4 + time5;
			
			timeleft = digi_rounder( timeleft, 1 );
			
			wait 0.05;
			
			level.hudelem_dltimer_value setvalue( timeleft );
			
			continue;
		}
		
		wait 0.05;
	}
}

digi_rounder( num, tenthsonly )
{
	//Adjusts num to total of 3 sigfigs if > 1, 2 sigfigs if < 1
	
	if( num >= 10 || isdefined( tenthsonly ) )
	{
		num = num * 10;
		num = int( num );
		num = num / 10;
	}
	else
	{
		num = num * 100;
		num = int( num );
		num = num / 100;
	}
	
	if( num >= 10 || isdefined( tenthsonly ) )
	{
		intnum = int( num );
		diff = num - intnum;
		if( diff == 0 )
		{
			num = num + 0.1;
		}
	}
	else
	if( num < 10 && num >= 1 )
	{
		//8.2 vs. 8.25
		//7.6 vs. 7.68
		
		bignum = num * 10;			//82 vs. 82.5
		checknum = int( num * 10 );	//82 vs. 82
		
		diff = bignum - checknum;	//0 & 0.5
		
		if( diff == 0 )
		{
			num = num + 0.01;
		}
	}
	else
	if( num < 1 && num >= 0.1 )
	{	
		intchecknum = int( num * 10 );	
		checknum = intchecknum / 10;	
		
		intnum = int( num * 100 );		
		intchecknum = int( checknum * 100 );	
		
		mod = intnum % intchecknum;	//mod only accepts integers
	
		if( mod == 0 )
		{
			num = num + 0.01;
		}
	}
	else
	if( num < 0.1 )
	{
		intchecknum = int( num * 100 );
		num = intchecknum / 100;
	}

	return( num );
}

dsm_health_regen()
{
	//regenerates the health of the dsm
	
	flag_wait( "download_started" );
	
	while( !flag( "dsm_recovered" ) )
	{
		boostedVal = level.dsmHealth + level.dsm_regen_amount;
		
		if( boostedVal >= level.dsmHealthMax )
		{
			//cap it
			level.dsmHealth = level.dsmHealthMax;
		}
		else
		{
			level.dsmHealth = boostedVal;
		}
		
		wait 1;
	}
}

dsm_health_regen_calc()
{
	//adjusts the regen per second amount of the dsm
	
	flag_wait( "download_started" );
	
	dsm_real = getent( "dsm", "targetname" );
	dsm_org = dsm_real.origin;
	
	while( !flag( "dsm_recovered" ) )
	{
		dist = length( level.player.origin - dsm_org );
		
		if( dist >= level.dsm_regen_dist_limit )
		{
			level.dsm_regen_amount = 0;
		 	wait 0.25;
		 	continue;
		}
		else
		{
			factor = ( level.dsm_regen_dist_limit - dist ) / level.dsm_regen_dist_limit;
			level.dsm_regen_amount = factor * level.dsm_regen_amount_max;
		}
		
		wait 0.25;
	}
}

dsm_destruction_damage_detect()
{
	level endon ( "dsm_recovered" );
	
	flag_wait( "download_started" );
	
	trig = getent( "dsm_dmg_trigger", "targetname" );

	while( !flag( "dsm_recovered" ) )
	{
		trig waittill ( "damage", amount, attacker );
		
		//stray shot protection on AI, AI must be within X units
		
		if( attacker != level.player )
		{
			dist = length( trig.origin - attacker.origin );
			
			if( dist < 512 )
			{
				level.dsmHealth = level.dsmHealth - amount;
			}
		}
		else
		{
			level.dsmHealth = level.dsmHealth - amount;
		}
		
		if( level.dsmHealth <= 500 )
		{
			flag_clear( "can_save" );
		}
		
		if( level.dsmHealth > 500 )
		{
			flag_set( "can_save" );
		}
		
		if( level.dsmHealth <= 0 )
		{
			if( attacker == level.player )
			{
				setdvar( "ui_deadquote", &"ESTATE_DSM_DESTROYED_BY_PLAYER" );
			}
			else
			{
				setdvar( "ui_deadquote", &"ESTATE_DSM_DESTROYED_BY_AI_GUNFIRE" );	
			}
			
			level notify ( "dsm_has_been_destroyed" );
			thread download_display_delete();
			
			missionFailedWrapper();	
		}
	}
}

/*

dsm_destruction_ai_trig( dsm )
{
	//When an enemy AI touches this trigger while the DSM is downloading, begin the dsm destruction process.
	
	self endon ( "death" );
	level endon ( "dsm_recovered" );
	
	trig = getent( "dsm_suicide_trig", "targetname" );
	
	while( 1 )
	{
		trig waittill( "trigger", other );
		if( other == self )
		{
			break;
		}
	}
	
	//Only one enemy AI can run the destruction function at a time, they get queued up
	
	level.dsmDestructionController function_stack( ::dsm_destruction_ai, self, dsm );
}

dsm_destruction_ai( guy, dsm )
{
	guy endon ( "death" );
	level endon ( "dsm_recovered" );
	
	//Player can prevent dsm destruction until this sound effect ends
	
	//dsm play_sound_on_entity( "RU_4_inform_attack_grenade" );
	
	screams = [];
	screams[ screams.size ] = "est_ru1_attack";
	screams[ screams.size ] = "est_ru2_attack";
	screams[ screams.size ] = "est_ru3_attack";
	screams[ screams.size ] = "est_ru4_attack";
	
	dsm play_sound_on_entity( screams[ randomint( screams.size ) ] );

	//This is the point of no return
	
	org = guy.origin;
	
	flag_set( "dsm_compromised" );
	
	level.ghost stop_magic_bullet_shield();
	level.ghost.health = 1;
	
	thread dsm_suicide_bombing( org, dsm );
}

dsm_suicide_bombing( org, dsm )
{
	//DSM suicide bombing fx
	
	level endon ( "dsm_recovered" );
	
	playFX( getfx( "suicide_bomber" ), org );
	detorg = spawn( "script_origin", org );
	detorg playsound( "clusterbomb_explode_default" );
	RadiusDamage( org, 512, 5000, 1000 );
	earthquake( 0.25, 1, org, 2000 );
	flag_set( "dsm_destroyed" );
	//dsm delete();
	//dsm hide();
	
	//wait 0.5;
	
	setdvar( "ui_deadquote", &"ESTATE_DSM_DESTROYED_BY_AI_DETONATION" );
	missionFailedWrapper();	
}

*/

//*************************************************
// FRIENDLY DEATH CONTROL
//*************************************************

//These are failsafes for friendly death if they don't die during the dsm defense
//If they are alive during the player escape, one is shot by enemies, the other is mortared, guaranteed, plus their health is dropped

friendly_death_cointoss()
{
	flag_wait( "player_is_escaping" );
	
	level.cointoss = cointoss();
	
	wait 0.1;
	
	flag_set( "cointoss_done" );
}

scarecrow_death_decide()
{
	self endon ( "death" );
	
	flag_wait( "cointoss_done" );
	
	if( level.cointoss )
	{
		self thread friendly_death_bullet();
	}
	else
	{
		self thread friendly_death_mortar();
	}
}

ozone_death_decide()
{
	self endon ( "death" );
	
	flag_wait( "cointoss_done" );
	
	if( level.cointoss )
	{
		self thread friendly_death_mortar();
	}
	else
	{
		self thread friendly_death_bullet();
	}
}

friendly_death_bullet()
{
	self endon ( "death" );
	
	flag_wait( "birchfield_cleared_sector2" );
	
	source = getent( "breach_tweak_start", "targetname" );
	sourceOrg = source.origin;
	
	MagicBullet( "cheytac", sourceOrg, self.origin + ( 0, 0, 60 ) );
	
	self kill();
}

friendly_death_mortar()
{
	self endon ( "death" );
	
	flag_wait( "point_of_no_return" );
	
	org = self.origin;
	
	playFX( level._effect[ "mortar" ][ "dirt" ], org );
	detorg = spawn( "script_origin", org );
	detorg playsound( "clusterbomb_explode_default" );
	
	self kill();
}

deathtalk_scarecrow()
{
	self waittill ( "death" );
	
	if( !flag( "player_is_escaping" ) )
	{
		//I'm hit - (static hiss) 	
		
		node = getnode( "scarecrow_earlydefense_start", "targetname" );
		playerCanSeeFriendly = SightTracePassed( level.player.origin, node.origin, true, undefined );
		dist = length( level.player.origin - node.origin );
		
		if( dist > 384 && !playerCanSeeFriendly )
		{
			radio_dialogue( "est_scr_imhit" );
		}
		
		startTime = gettime();
		
		flag_waitopen( "strike_package_birchfield_dialogue" );
		flag_waitopen( "strike_package_bighelidrop_dialogue" );
		flag_waitopen( "strike_package_boathouse_dialogue" );
		flag_waitopen( "strike_package_solarfield_dialogue" );
		flag_waitopen( "strike_package_md500rush_dialogue" );
		flag_waitopen( "rpg_stables_dialogue" );
		flag_waitopen( "rpg_boathouse_dialogue" );
		flag_waitopen( "rpg_southwest_dialogue" );
		flag_waitopen( "ozone_death_dialogue" );
		flag_waitopen( "sniper_breaktime_dialogue" );
	
		timeLimit = 0.5 * 1000;
		clearedTime = gettime();
		timeElapsed = clearedTime - startTime;
		
		if( timeElapsed <= timeLimit )
		{
			flag_set( "scarecrow_death_dialogue" );
		
			//Scarecrow is down, I repeat Scarecrow is down!	
			radio_dialogue( "est_snp1_scarecrowdown" );
			
			flag_clear( "scarecrow_death_dialogue" );
		}
	}
}

deathtalk_ozone()
{
	self waittill ( "death" );
	
	if( !flag( "player_is_escaping" ) )
	{
		//Aaagh! I'm hit!!! Need assis- (static hiss)	
		radio_dialogue( "est_ozn_imhit" );
		
		startTime = gettime();
		
		flag_waitopen( "strike_package_birchfield_dialogue" );
		flag_waitopen( "strike_package_bighelidrop_dialogue" );
		flag_waitopen( "strike_package_boathouse_dialogue" );
		flag_waitopen( "strike_package_solarfield_dialogue" );
		flag_waitopen( "strike_package_md500rush_dialogue" );
		flag_waitopen( "rpg_stables_dialogue" );
		flag_waitopen( "rpg_boathouse_dialogue" );
		flag_waitopen( "rpg_southwest_dialogue" );
		flag_waitopen( "scarecrow_death_dialogue" );
		flag_waitopen( "sniper_breaktime_dialogue" );
		
		timeLimit = 0.5 * 1000;
		clearedTime = gettime();
		timeElapsed = clearedTime - startTime;
		
		if( timeElapsed <= timeLimit )
		{
			flag_set( "ozone_death_dialogue" );
		
			//Ozone is down!	
			radio_dialogue( "est_snp1_ozoneisdown" );	
			
			flag_clear( "ozone_death_dialogue" );
		}
	}
}
	
//****************************************//
// ABANDONMENT UTILS
//****************************************//

abandonment_start_monitor()
{
	flag_wait( "strike_packages_definitely_underway" );
	
	//If player is already in the desertion zone when we start monitoring for desertion
	//Wait for him to leave the desertion zone and then start enforcing failure
	
	//Otherwise, start enforcing failure immediately
	
	if( flag( "abandonment_danger_zone" ) )
	{
		flag_waitopen( "abandonment_danger_zone" );	
		
		flag_set( "player_can_fail_by_desertion" );
	}
	else
	{
		flag_set( "player_can_fail_by_desertion" );
	}
}

abandonment_early_escape_timer()
{
	//If player is already in the desertion zone when we start monitoring for desertion
	//Give player some amount of time to get out of the zone before we start enforcing failure
	
	level endon ( "dsm_recovered" );
	
	flag_wait( "strike_packages_definitely_underway" );	
	
	wait level.early_escape_timelimit;
	
	flag_set( "player_can_fail_by_desertion" );
}

abandonment_exit_tracking()
{
	level endon ( "dsm_recovered" );
	level endon ( "player_deserted_the_area" );
	
	flag_wait( "strike_packages_definitely_underway" );
	
	while( 1 )
	{
		flag_waitopen( "abandonment_danger_zone" );	
		level notify ( "player_is_out_of_danger_zone" );
		flag_wait( "abandonment_danger_zone" );
	}
}

abandonment_main_check()
{
	//Begins when we start enforcing failure officially
	
	level endon ( "dsm_recovered" );
	level endon ( "player_deserted_the_area" );
	
	flag_wait( "strike_packages_definitely_underway" );
	
	//trigger_multiple_flag_set_touching detect player at danger zone
		//clear save
		//start timer until abandonment trigger is untouched
		
	while( 1 )
	{
		flag_wait( "abandonment_danger_zone" );
		
		flag_clear( "can_save" );
		thread abandonment_danger_zone_timer();			
		flag_waitopen( "abandonment_danger_zone" );
		flag_set( "can_save" );
	}
}

abandonment_danger_zone_timer()
{
	level endon ( "dsm_recovered" );
	level endon ( "player_deserted_the_area" );
	level endon ( "player_is_out_of_danger_zone" );
	
	//play initial danger dialogue
	//play serious warning dialogue

	if( !level.usedFirstWarning )
	{
		//Roach! Don't stray too far from the safehouse! We need to protect the transfer!		
		thread radio_dialogue( "est_gst_dontstray" );
		
		level.usedFirstWarning = true;
	}
	else
	{
		//Roach! Stay the close to the house!		
		thread radio_dialogue( "est_gst_stayclose" );
		
		level.usedFirstWarning = false;
	}
	
	wait level.dangerZoneTimeLimit / 2;
	
	if( !level.usedSecondWarning )
	{
		//Roach, where the hell are you?! Fall back to the house! Move!	
		thread radio_dialogue( "est_gst_fallback" );
	
		level.usedSecondWarning = true;
	}
	else
	{
		//Roach, pull back to the safehouse! They're trying to stop the transfer!		
		thread radio_dialogue( "est_gst_tryingtostop" );
	
		level.usedSecondWarning = false;
	}
	
	wait level.dangerZoneTimeLimit / 2;
	
	//fail the mission if the time limit is reached and player has not exited the danger zone
	flag_set( "player_entered_autofail_zone" );
}

abandonment_failure()
{	
	flag_wait( "player_can_fail_by_desertion" );
	
	flag_wait( "player_entered_autofail_zone" );
	
	//trigger_multiple_flag_set_touching detect player into fail zone
	//play failure dialogue
	//fail mission	
	
	level notify ( "player_deserted_the_area" );
	
	//Roach! They've destroyed the DSM!! They've destroyed-(static hiss)	
	radio_dialogue( "est_gst_destroyedthedsm" );
	radio_dialogue_stop();
	
	setdvar( "ui_deadquote", &"ESTATE_DSM_DESTROYED_BY_DESERTION" );	
	
	missionFailedWrapper();
	
	//Maybe I'll use this if I need it
	//There's too many of them in here! Roach!!! We've lost the DSM, I repeat we've lost-(static hiss)		
	//radio_dialogue( "est_gst_lostthedsm" );
}
	
//****************************************//
// SMOKESCREEN UTILS
//****************************************//

smokescreen( smokepotName )
{
	smokeMachines = getentarray( smokepotName, "targetname" );
	foreach( machine in smokeMachines )
	{
		machine thread smokepot();
	}
}

smokepot()
{
	playFX( getfx( "smoke_cloud" ), self.origin );
	self playsound( "estate_smokegrenade_explode" );
}

//****************************************//
// VISION SET UTILS
//****************************************//

blackOut( duration, blur )
{
	//wait duration;
	self fadeOverlay( duration, 1, blur );
}

grayOut( duration, blur )
{
	//wait duration;
	self fadeOverlay( duration, 0.6, blur );
}

restoreVision( duration, blur )
{
	//wait duration;
	self fadeOverlay( duration, 0, blur );
}

fadeOverlay( duration, alpha, blur )
{
	self fadeOverTime( duration );
	self.alpha = alpha;
	setblur( blur, duration );
	wait duration;
}

//****************************************//
// HINT PRINT UTILS
//****************************************//

should_break_claymore_hint()
{
	weapon = level.player GetCurrentWeapon();
	
	if( weapon == "claymore" )
	{
		return( true );
	}
	
	if( !( level.player buttonpressed( "DPAD_RIGHT" ) ) )
	{
		return( false );
	}
	else
	{
		return( true );
	}
}


estate_autosave_proximity_threat_func( enemy )
{
	foreach ( player in level.players )
	{
		dist = Distance( enemy.origin, player.origin );
		v_dist = abs( enemy.origin[2] - player.origin[2] );
		
		if ( dist < 360 && v_dist < 200 )
		{
			/# maps\_autosave::AutoSavePrint( "autosave failed: AI too close to player" ); #/
			return "return";
		}
		else
		if ( dist < 1000 )
		{
			return "threat_exists";
		}
	}
	
	return "none";
}