6113 lines
155 KiB
Plaintext
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";
|
|
} |