IW4-Dump-Files/maps/estate_code.gsc

6113 lines
155 KiB
Plaintext

#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";
}