#include maps\_utility; #include common_scripts\utility; #include maps\_hud_util; #include maps\_specialops; /*QUAKED info_player_start_pmc (0.8 0.7 0.2) (-16 -16 0) (16 16 72) Players spawn at these locations in PMC games.*/ /*QUAKED info_player_start_pmcDefend (0.8 0.2 0.2) (-16 -16 0) (16 16 72) Players spawn at these locations in defend PMC games, place near info_volumes with "defend_obj" targetnames.*/ /*QUAKED info_volume_pmcDefend (0.12 0.23 1.0) ? defaulttexture="volume" Obj is to keep enemies out of this volume, place near defend player_starts and around pmc objective prefabs.*/ /*QUAKED script_model_pickup_claymore (1 0 0) (-32 -16 0) (32 16 24) ORIENT_LOD NO_SHADOW NO_STATIC_SHADOWS defaultmdl="weapon_claymore" default:"model" "weapon_claymore" */ DEFEND_ENEMY_BUILDUP_COUNT_FRACTION = 0.9; MIN_SPAWN_DISTANCE = 1024; DEFAULT_ENEMY_GOAL_RADIUS_MIN = 512; DEFAULT_ENEMY_GOAL_RADIUS_MAX = 2500; DEFAULT_ENEMY_GOAL_HEIGHT = 128; DEFAULT_ENEMY_GOAL_HEIGHT_JUGGERNAUT = 81; DEFAULT_ENEMY_GOAL_HEIGHT_SNIPER = 640; ENEMY_GOAL_RADIUS_SEEK_PLAYER_MIN = 1200; ENEMY_GOAL_RADIUS_SEEK_PLAYER_MAX = 1600; SEEK_PLAYERS_ENEMY_COUNT = 6; TIME_REMAINING_FLASH_WAYPOINT = 2 * 60; AI_INSIDE_TRANSPORT_CHOPPER = 6; // This shouldn't be changed, it's only for info, it doesn't actually change the number that go into the chopper MAX_ENEMIES_ALIVE_ELIMINATION = 25; DEFEND_SETUP_TIME = 180; DEFEND_TIME = 5 * 60; JUGGERNAUT_ENEMY_VISIBLE_TIMEOUT = 20; preLoad() { level.pmc_match = true; level.teambased = false; maps\_juggernaut::main(); level._effect[ "extraction_smoke" ] = loadfx( "smoke/signal_smoke_green" ); } main() { assert( isdefined( level.pmc_gametype ) ); assert( isdefined( level.pmc_enemies ) ); assert( level.pmc_enemies > 0 ); if ( isdefined( level.pmc_enemies_alive ) ) assert( level.pmc_enemies_alive > 0 ); if ( isDefendMatch() ) { assert( isdefined( level.pmc_defend_enemy_count ) ); assert( level.pmc_defend_enemy_count > 0 ); } if ( !isdefined( level.pmc_alljuggernauts ) ) level.pmc_alljuggernauts = false; //------------------------------------------------------------- common_scripts\_sentry::main(); if ( !is_specialop() ) maps\_specialops_code::pick_starting_location_pmc(); initialize_gametype(); start_pmc_gametype(); } initialize_gametype() { juggernaut_setup(); //-------------------------------- // Precache //-------------------------------- // Enemies Alive: &&1 precacheString( &"PMC_DEBUG_ENEMY_COUNT" ); // Vehicles Alive: &&1 precacheString( &"PMC_DEBUG_VEHICLE_COUNT" ); // Enemy Spawners: &&1 precacheString( &"PMC_DEBUG_SPAWNER_COUNT" ); // Enemies remaining: &&1 precacheString( &"PMC_ENEMIES_REMAINING" ); // Time Remaining: &&1 precacheString( &"PMC_TIME_REMAINING" ); // Kill all enemies in the level. precacheString( &"PMC_OBJECTIVE_KILL_ENEMIES" ); // Kill all enemies in the level [ &&1 Remaining ]. precacheString( &"PMC_OBJECTIVE_KILL_ENEMIES_REMAINING" ); // Enter the abort codes into the laptop before time runs out. precacheString( &"PMC_OBJECTIVE_ABORT_CODES" ); // Mission failed. The objective was not completed in time. precacheString( &"PMC_OBJECTIVE_FAILED" ); // Spectating precacheString( &"PMC_SPECTATING" ); // Reach the extraction zone before time runs out. precacheString( &"PMC_OBJECTIVE_EXTRACT" ); // Press and hold &&1 to use the laptop. precacheString( &"PMC_HINT_USELAPTOP" ); // Set up a defensive position before enemy attack. precacheString( &"PMC_OBJECTIVE_SETUP_DEFENSES" ); // Time until attack: &&1 precacheString( &"PMC_TIME_UNTIL_ATTACK" ); // Survive until time runs out. precacheString( &"PMC_OBJECTIVE_DEFEND" ); // Press and hold &&1 on the laptop to skip set up time. precacheString( &"PMC_START_ATTACK_USE_HINT" ); // Approach the laptop to skip set up time. precacheString( &"PMC_START_ATTACK_HINT" ); // HQ Damage precacheString( &"PMC_HQ_DAMAGE" ); // Retrieving Intel... precacheString( &"PMC_HQ_RECOVERING_INTEL" ); precacheModel( "com_laptop_2_open_obj" ); precacheShader( "waypoint_ammo" ); precacheShader( "waypoint_target" ); precacheShader( "waypoint_extraction" ); precacheShader( "waypoint_defend" ); //-------------------------------- // Set up some tweak values //-------------------------------- setDvarIfUninitialized( "pmc_debug", "0" ); setDvarIfUninitialized( "pmc_debug_forcechopper", "0" ); level.pmc = spawnStruct(); level.pmc.hud = spawnStruct(); level.pmc.sound = []; // level.pmc.music = []; level.pmc.defendSetupTime = DEFEND_SETUP_TIME; level.pmc.defendTime = DEFEND_TIME; if ( isdefined( level.pmc_enemies_alive ) ) level.pmc.max_ai_alive = level.pmc_enemies_alive; else level.pmc.max_ai_alive = MAX_ENEMIES_ALIVE_ELIMINATION; level.pmc.enemy_goal_radius_min = DEFAULT_ENEMY_GOAL_RADIUS_MIN; level.pmc.enemy_goal_radius_max = DEFAULT_ENEMY_GOAL_RADIUS_MAX; // level.pmc.music[ "exfiltrate" ] = "pmc_music_extract"; // level.pmc.music[ "mission_complete" ] = "pmc_victory_music"; level.pmc.sound[ "exfiltrate" ] = "pmc_exfiltrate_area"; level.pmc.sound[ "obj_win" ] = "pmc_outta_here"; level.pmc.sound[ "obj_fail" ] = "pmc_mission_failed"; level.pmc.sound[ "minutes_6" ] = "pmc_6_minutes"; level.pmc.sound[ "minutes_4" ] = "pmc_4_minutes"; level.pmc.sound[ "minutes_3" ] = "pmc_3_minutes"; level.pmc.sound[ "minutes_2" ] = "pmc_2_minutes"; level.pmc.sound[ "minutes_1" ] = "pmc_1_minute"; level.pmc.sound[ "minutes_30sec" ][ 0 ] = "pmc_time_almost_up"; level.pmc.sound[ "minutes_30sec" ][ 1 ] = "pmc_running_out_of_time"; level.pmc.sound[ "timer_tick" ] = "pmc_timer_tick"; // level.pmc.sound[ "juggernaut_attack" ] = "pmc_juggernaut"; //-------------------------------- // Variables and Init //-------------------------------- flag_init( "enemies_seek_players" ); flag_init( "objective_complete" ); flag_init( "extraction_complete" ); flag_init( "defend_started" ); flag_init( "pmc_defend_setup_time_finished" ); flag_init( "defend_failed" ); flag_init( "remove_caches" ); flag_init( "exfiltrate_music_playing" ); flag_init( "mission_complete" ); flag_init( "mission_start" ); flag_init( "staged_pacing_used" ); flag_init( "pacing_stage_2" ); flag_init( "pacing_stage_3" ); // delete the stuff that is only for hostage mode for now. run_thread_on_noteworthy( "hostage_only", ::self_delete ); // Make slowmo breach spawners run the PMC logic run_thread_on_noteworthy( "pmc_spawner", ::add_spawn_function, ::room_breach_spawned ); level.pmc.helicopter_exists = false; level.pmc.helicopter_queuing = false; level.pmc.helicopter_transport_last = undefined; level.pmc.helicopter_transport_min_time = 90 * 1000;// seconds * 1000 level.pmc.helicopter_attack_last = undefined; level.pmc.helicopter_attack_min_time = 90 * 1000;// seconds * 1000 level.pmc.enemy_vehicles_alive = 0; level.sentry_pickups = getentarray( "script_model_pickup_sentry_gun", "classname" ); level.sentry_pickups = array_combine( level.sentry_pickups, getentarray( "script_model_pickup_sentry_minigun", "classname" ) ); level.pmc.send_in_juggernaut = false; level.pmc.juggernauts_spawned = 0; level.pmc.juggernauts_killed = 0; level.pmc.spawned_juggernaut_at_game_start = false; level.pmc.spawned_juggernaut_at_game_start_counter = 5; } start_pmc_gametype() { set_gametype_vars(); thread fade_challenge_in(); thread fade_challenge_out( "mission_complete" ); if ( !is_specialop() ) { maps\_autosave::_autosave_game_now_nochecks(); wait 0.05; // wait for the autosave to happen before opening the menu or you get stuck in unusable menu when the save loads } //if ( !isDefendMatch() ) // array_thread( level.sentry_pickups, ::delete_sentry_pickup ); //get an array of pre-placed enemy sentry guns level.sentry_enemies = getentarray( "sentry_gun", "targetname" ); level.sentry_enemies = array_combine( level.sentry_enemies, getentarray( "sentry_minigun", "targetname" ) ); if ( isDefendMatch() ) array_thread( level.sentry_enemies, common_scripts\_sentry::delete_sentry_turret ); //---------------------------------------- // Set gametype specific function pointers //---------------------------------------- level.pmc.enemy_spawn_position_func = ::pick_enemy_spawn_positions; level.pmc.populate_enemies_func = ::populate_enemies; level.pmc.get_spawnlist_func = ::get_spawnlist; level.pmc.set_goal_func = ::enemy_set_goal_when_player_spotted; level.pmc.limitRespawns = true; if ( isDefendMatch() ) { level.pmc.enemy_spawn_position_func = ::pick_enemy_spawn_positions_defend; level.pmc.populate_enemies_func = ::populate_enemies; level.pmc.get_spawnlist_func = ::get_spawnlist_defend; level.pmc.set_goal_func = ::enemy_seek_objective_in_stages; level.pmc.limitRespawns = false; } else thread staged_pacing_system();// obj and elimination, not defend assert( isdefined( level.pmc.enemy_spawn_position_func ) ); assert( isdefined( level.pmc.populate_enemies_func ) ); assert( isdefined( level.pmc.get_spawnlist_func ) ); assert( isdefined( level.pmc.set_goal_func ) ); //---------------------------------------- // Get all enemy spawners in level //---------------------------------------- // Get all enemy spawners in the level for possible spawning level.pmc.enemy_spawners_full_list = getentarray( "pmc_spawner", "targetname" ); assertEx( level.pmc.enemy_spawners_full_list.size >= level.pmc_enemies, "There aren't enough enemy spawners in the level." ); [[ level.pmc.enemy_spawn_position_func ]](); assert( isdefined( level.pmc.enemy_spawners ) ); assert( level.pmc.enemy_spawners.size > 0 ); assert( isdefined( level.pmc.enemy_spawners_full_list ) ); assert( level.pmc.enemy_spawners_full_list.size > 0 ); debug_print( "Found " + level.pmc.enemy_spawners.size + " enemy spawners" ); assertEx( level.pmc.enemy_spawners.size >= level.pmc.enemies_kills_to_win, "There aren't enough enemy spawners in the level to hunt down " + level.pmc.enemies_kills_to_win + " enemies." ); level.pmc.enemies_remaining = level.pmc.enemies_kills_to_win; //---------------------------------------- // Get us started! //---------------------------------------- level.pmc._populating_enemies = false; level.pmc._re_populating_enemies = false; gametype_setup(); setup_objective_entities(); add_player_objectives(); thread [[ level.pmc.populate_enemies_func ]](); //---------------------------------------- // Debug threads for testing //---------------------------------------- if ( getdvar( "pmc_debug" ) == "1" ) { thread debug_show_enemy_spawners_count(); thread debug_show_enemies_alive_count(); thread debug_show_vehicles_alive_count(); } if ( !is_specialop() ) { // Wait till frame end so that the flags can get initialized in other scripts before getting set below wait 0.05; flag_set( "disable_autosaves" ); } } set_gametype_vars() { /# // make sure the gametype is valid switch( level.pmc_gametype ) { case "mode_elimination": debug_print( "Gametype: Elimination" ); break; case "mode_objective": debug_print( "Gametype: Objective" ); break; case "mode_defend": debug_print( "Gametype: Defend" ); break; default: assertMsg( "Error selecting gametype" ); } #/ level.pmc.enemies_kills_to_win = level.pmc_enemies; assert( level.pmc.enemies_kills_to_win > 0 ); if ( isDefendMatch() ) level.pmc.max_ai_alive = level.pmc_defend_enemy_count; } pick_enemy_spawn_positions_defend() { level.pmc.enemy_spawners = level.pmc.enemy_spawners_full_list; } pick_enemy_spawn_positions() { color_gray = ( 0.3, 0.3, 0.3 ); color_red = ( 1, 0, 0 ); color_white = ( 1, 1, 1 ); color_blue = ( 0, 0, 1 ); color_green = ( 0, 1, 0 ); // get min and max x and y values for all possible spawners x_min = level.pmc.enemy_spawners_full_list[ 0 ].origin[ 0 ]; x_max = level.pmc.enemy_spawners_full_list[ 0 ].origin[ 0 ]; y_min = level.pmc.enemy_spawners_full_list[ 0 ].origin[ 1 ]; y_max = level.pmc.enemy_spawners_full_list[ 0 ].origin[ 1 ]; foreach ( spawner in level.pmc.enemy_spawners_full_list ) { if ( spawner.origin[ 0 ] < x_min ) x_min = spawner.origin[ 0 ]; if ( spawner.origin[ 0 ] > x_max ) x_max = spawner.origin[ 0 ]; if ( spawner.origin[ 1 ] < y_min ) y_min = spawner.origin[ 1 ]; if ( spawner.origin[ 1 ] > y_max ) y_max = spawner.origin[ 1 ]; } x_min -= 250; x_max += 250; y_min -= 250; y_max += 250; // Occassional wait to prevent false infinite loop since this can't always be done on the first frame // It is however, always done while the player has a black screen overlay and isn't in gameplay wait 0.05; // Draw the bounds of the area if ( getdvar( "pmc_debug" ) == "1" ) { topLeft = ( x_min - 50, y_max + 50, 0 ); topRight = ( x_max + 50, y_max + 50, 0 ); bottomLeft = ( x_min - 50, y_min - 50, 0 ); bottomRight = ( x_max + 50, y_min - 50, 0 ); thread draw_line( topLeft, topRight, 1, 0, 0 ); thread draw_line( topRight, bottomRight, 1, 0, 0 ); thread draw_line( bottomRight, bottomLeft, 1, 0, 0 ); thread draw_line( bottomLeft, topLeft, 1, 0, 0 ); } // Set the number of divisions we will make to the area number_of_divisions = 5; // Find how tall and wide each grid space will be with the given number of divisions width_x = abs( x_max - x_min ); width_y = abs( y_max - y_min ); division_spacing_x = width_x / number_of_divisions; division_spacing_y = width_y / number_of_divisions; averageQuadRadius = ( division_spacing_x + division_spacing_y ) / 4; level.pmc.enemy_goal_radius_min = int( averageQuadRadius * 0.8 ); level.pmc.enemy_goal_radius_max = int( averageQuadRadius * 1.2 ); // Create a struct for each grid square so we can store info on each one numQuads = number_of_divisions * number_of_divisions; level.quads = []; curent_division_x = 0; curent_division_y = 0; for ( i = 0 ; i < numQuads ; i++ ) { level.quads[ i ] = spawnStruct(); level.quads[ i ].number = i; level.quads[ i ].containsSpawners = false; level.quads[ i ].enemiesInQuad = 0; level.quads[ i ].min_x = x_min + ( division_spacing_x * curent_division_x ); level.quads[ i ].max_x = level.quads[ i ].min_x + division_spacing_x; level.quads[ i ].min_y = y_max - ( division_spacing_y * curent_division_y ); level.quads[ i ].max_y = level.quads[ i ].min_y - division_spacing_y; curent_division_x++ ; if ( curent_division_x >= number_of_divisions ) { curent_division_x = 0; curent_division_y++ ; } } // see which quads don't have any spawners in them at all // so they aren't used in spawn logic foreach ( spawner in level.pmc.enemy_spawners_full_list ) { if ( distance( getAveragePlayerOrigin(), spawner.origin ) <= MIN_SPAWN_DISTANCE ) continue; quadIndex = spawner get_quad_index(); level.quads[ quadIndex ].containsSpawners = true; } // Occassional wait to prevent false infinite loop since this can't always be done on the first frame // It is however, always done while the player has a black screen overlay and isn't in gameplay wait 0.05; // print the quad bounds and number on each quad if ( getdvar( "pmc_debug" ) == "1" ) { foreach ( quad in level.quads ) { topLeft = ( quad.min_x, quad.max_y, 0 ); topRight = ( quad.max_x, quad.max_y, 0 ); bottomLeft = ( quad.min_x, quad.min_y, 0 ); bottomRight = ( quad.max_x, quad.min_y, 0 ); textOrgX = quad.min_x + ( division_spacing_x / 2 ); textOrgY = quad.min_y - ( division_spacing_y / 2 ); lineColor = color_blue; textColor = color_white; if ( !quad.containsSpawners ) continue; thread draw_line( topLeft, topRight, lineColor[ 0 ], lineColor[ 1 ], lineColor[ 2 ] ); thread draw_line( topRight, bottomRight, lineColor[ 0 ], lineColor[ 1 ], lineColor[ 2 ] ); thread draw_line( bottomRight, bottomLeft, lineColor[ 0 ], lineColor[ 1 ], lineColor[ 2 ] ); thread draw_line( bottomLeft, topLeft, lineColor[ 0 ], lineColor[ 1 ], lineColor[ 2 ] ); print3d( ( textOrgX, textOrgY, 0 ), string( quad.number ), textColor, 1.0, 5.0, 100000 ); } } randomized = undefined; // if most of the spawners in the map will be used then just throw all of them into the pool if ( level.pmc.enemies_kills_to_win >= ( level.pmc.enemy_spawners_full_list.size / 1.2 ) ) { spawnsToUse = level.pmc.enemy_spawners_full_list; debug_print( "Using all spawners placed in the map as possible enemy locations because we're using almost all of the spawners!" ); } else { // We now know which quads contain possible spawners so lets put one spawner in each quad to start, then two once all quads are used, then three, etc randomized = array_randomize( level.pmc.enemy_spawners_full_list ); spawnsToUse = []; allowedSpawnersPerQuad = 1; loopCount = 0; for ( ;; ) { loopCount = 0; foreach ( spawner in randomized ) { if ( distance( getAveragePlayerOrigin(), spawner.origin ) <= MIN_SPAWN_DISTANCE ) continue; quadIndex = spawner get_quad_index(); assert( level.quads[ quadIndex ].containsSpawners ); assert( isdefined( level.quads[ quadIndex ].enemiesInQuad ) ); // If this quad has already been used once we don't use it again if ( level.quads[ quadIndex ].enemiesInQuad >= allowedSpawnersPerQuad ) continue; // This spawner is in a quad that hasn't been used yet so we can use it level.quads[ quadIndex ].enemiesInQuad++ ; // Add this spawner to the spawnsToUse array, and take it out of the future potential spawner list spawnsToUse[ spawnsToUse.size ] = spawner; randomized = array_remove( randomized, spawner ); // If we've reached the number of enemies to kill then move on if ( spawnsToUse.size >= level.pmc.enemies_kills_to_win ) break; loopCount++ ; if ( loopCount > 50 ) { loopCount = 0; // Occassional wait to prevent false infinite loop since this can't always be done on the first frame // It is however, always done while the player has a black screen overlay and isn't in gameplay wait 0.05; } } allowedSpawnersPerQuad++ ; if ( spawnsToUse.size >= level.pmc.enemies_kills_to_win ) break; debug_print( "Still need more spawners" ); } assert( spawnsToUse.size > 0 ); assert( spawnsToUse.size <= level.pmc.enemies_kills_to_win ); assert( ( spawnsToUse.size + randomized.size ) == level.pmc.enemy_spawners_full_list.size ); randomized = undefined; } debug_print( "All spawners are ready" ); if ( getdvar( "pmc_debug" ) == "1" ) { foreach ( spawner in spawnsToUse ) thread draw_line( spawner.origin, spawner.origin + ( 0, 0, 250 ), color_green[ 0 ], color_green[ 1 ], color_green[ 2 ] ); if ( isdefined( randomized ) ) { foreach ( spawner in randomized ) thread draw_line( spawner.origin, spawner.origin + ( 0, 0, 50 ), color_red[ 0 ], color_red[ 1 ], color_red[ 2 ] ); } } level.pmc.enemy_spawners = spawnsToUse; } get_quad_index() { org_x = self.origin[ 0 ]; org_y = self.origin[ 1 ]; quadIndex = undefined; foreach ( quad in level.quads ) { assert( isdefined( quad.number ) ); if ( ( org_x >= quad.min_x ) && ( org_x <= quad.max_x ) && ( org_y >= quad.max_y ) && ( org_y <= quad.min_y ) ) { assert( quad.number >= 0 ); assert( quad.number < level.quads.size ); return quad.number; } } assertMsg( "Quad wasn't found in get_quad_index()" ); } populate_enemies() { if ( is_specialop() ) flag_wait( "mission_start" ); //--------------------------------------------------------------------- // Spawns all of the best located AI until the max alive AI count is reached //--------------------------------------------------------------------- if ( level.pmc._populating_enemies ) return; level.pmc._populating_enemies = true; if ( isDefendMatch() ) flag_wait( "pmc_defend_setup_time_finished" ); prof_begin( "populate_enemies" ); debug_print( "Populating enemies" ); aliveEnemies = getaiarray( "axis" ); assert( isdefined( aliveEnemies ) ); assert( aliveEnemies.size + level.pmc.enemy_vehicles_alive <= level.pmc.max_ai_alive ); if ( level.pmc.limitRespawns ) assert( aliveEnemies.size + level.pmc.enemy_vehicles_alive <= level.pmc.enemies_remaining ); // Make sure we don't already have enough AI alive to beat the level if ( level.pmc.limitRespawns ) { if ( aliveEnemies.size + level.pmc.enemy_vehicles_alive >= level.pmc.enemies_remaining ) { level.pmc._populating_enemies = false; return; } } // Make sure we don't already have the most AI alive that we can have if ( aliveEnemies.size + level.pmc.enemy_vehicles_alive >= level.pmc.max_ai_alive ) { level.pmc._populating_enemies = false; return; } // See how many AI we have room to spawn numberToSpawn = level.pmc.max_ai_alive - ( aliveEnemies.size + level.pmc.enemy_vehicles_alive ); freeAISlots = getFreeAICount(); if ( numberToSpawn > freeAISlots ) numberToSpawn = freeAISlots; assert( numberToSpawn > 0 ); if ( isDefendMatch() ) { if ( ( numberToSpawn + AI_INSIDE_TRANSPORT_CHOPPER ) < ( level.pmc.max_ai_alive * DEFEND_ENEMY_BUILDUP_COUNT_FRACTION ) ) { level.pmc._populating_enemies = false; return; } } // Again, make sure that the new amount of AI we're about to spawn, plus the alive AI, doesn't // exceed the number of AI remaining to win. Cap numberToSpawn if required. if ( level.pmc.limitRespawns ) { if ( aliveEnemies.size + level.pmc.enemy_vehicles_alive + numberToSpawn > level.pmc.enemies_remaining ) numberToSpawn = level.pmc.enemies_remaining - ( aliveEnemies.size + level.pmc.enemy_vehicles_alive ); assert( numberToSpawn > 0 ); } // Make sure that we don't spawn AI on the ground if a chopper is in queue, because it's waiting to fill up if ( level.pmc.helicopter_queuing ) { if ( numberToSpawn >= AI_INSIDE_TRANSPORT_CHOPPER ) level notify( "spawn_chopper" ); level.pmc._populating_enemies = false; debug_print( numberToSpawn + " AI are in chopper queue" ); return; } spawnList = [[ level.pmc.get_spawnlist_func ]](); // Spawn the spawnList spawn_more_enemies( spawnList, numberToSpawn ); prof_end( "populate_enemies" ); level.pmc._populating_enemies = false; } get_spawnlist() { // Get array of enemy spawners in order of closest to farthest return get_array_of_closest( getAveragePlayerOrigin(), level.pmc.enemy_spawners, undefined, undefined, undefined, MIN_SPAWN_DISTANCE ); } get_spawnlist_defend() { // Get array of enemy spawners in order of farthest to closest all_spawners = get_array_of_closest( getAveragePlayerOrigin(), level.pmc.enemy_spawners_full_list, undefined, undefined, undefined, MIN_SPAWN_DISTANCE ); all_spawners = array_reverse( all_spawners ); //use the farthest second half spawners = []; for ( i = 0 ; i < int( all_spawners.size / 2 );i++ ) { spawners[ spawners.size ] = all_spawners[ i ]; } if ( getdvar( "pmc_debug" ) == "1" ) { level notify( "updated_spawn_list" ); foreach ( spawner in spawners ) thread draw_line_until_notify( spawner.origin, spawner.origin + ( 0, 0, 250 ), 0, 1, 0, level, "updated_spawn_list" ); } return array_randomize( spawners ); } re_populate_enemies() { if ( level.pmc._re_populating_enemies ) return; level.pmc._re_populating_enemies = true; wait 3.0; [[ level.pmc.populate_enemies_func ]](); level.pmc._re_populating_enemies = false; } juggernaut_setup() { level.jug_spawners = undefined; level.juggernaut_mode = false; level.juggernaut_next_spawner = 0; jugs = getentarray( "juggernaut_spawner", "targetname" ); if ( isdefined( jugs ) && jugs.size > 0 ) { level.juggernaut_mode = true; level.jug_spawners = jugs; } } spawn_juggernaut( reg_spawner ) { jug_spawner = level.jug_spawners[ level.juggernaut_next_spawner ]; level.juggernaut_next_spawner++ ; if ( level.juggernaut_next_spawner >= level.jug_spawners.size ) { // wait a frame so we don't try to use the same spawner twice in one frame wait 0.05; level.juggernaut_next_spawner = 0; } jug_spawner.origin = reg_spawner.origin; jug_spawner.angles = reg_spawner.angles; jug_spawner.count = 1; guy = jug_spawner spawn_ai(); return guy; } init_enemy_combat_mode( spawnerIndex ) { if ( isDefendMatch() ) return; /# if ( getdvar( "scr_force_ai_combat_mode" ) != "0" ) return; #/ if ( self animscripts\combat_utility::isLongRangeAI() ) return; if ( self animscripts\combat_utility::isShotgunAI() ) return; if ( spawnerIndex % 3 ) self.combatMode = "ambush"; } spawn_more_enemies( spawnList, numberToSpawn ) { debug_print( "Trying to spawn " + numberToSpawn + " enemies" ); // Try spawning from spawners that haven't been used yet if possible. numberSpawnedCorrectly = 0; numberSpawnedCorrectly = spawn_more_enemies_from_array( spawnList, numberToSpawn ); // Try spawning remaining ( if any ) from any spawners, instead of just ones that haven't been used yet. numberSpawnedIncorrectly = 0; if ( numberSpawnedCorrectly < numberToSpawn ) numberSpawnedIncorrectly = spawn_more_enemies_from_array( level.pmc.enemy_spawners_full_list, numberToSpawn - numberSpawnedCorrectly, false ); assertEx( numberSpawnedCorrectly + numberSpawnedIncorrectly == numberToSpawn, "There are enough spawn locations in the level, but none of them could be used for spawning" ); debug_print( "Successfully spawned " + ( numberSpawnedCorrectly + numberSpawnedIncorrectly ) + " enemies, after retrying " + numberSpawnedIncorrectly + " failed attempts." ); debug_print( "Possible spawners remaining: " + level.pmc.enemy_spawners.size ); } spawn_more_enemies_from_array( spawnList, numberToSpawn, removeSpawnedFromArray ) { if ( !isdefined( removeSpawnedFromArray ) ) removeSpawnedFromArray = true; if ( isDefendMatch() ) removeSpawnedFromArray = false; spawnersUsed = []; numberFailedAttempts = 0; numberToSpawnRemaining = numberToSpawn; for ( i = 0; i < spawnList.size; i++ ) { isjuggernaut = false; spawnList[ i ].count = 1; if ( should_spawn_juggernaut() ) { guy = spawn_juggernaut( spawnList[ i ] ); isjuggernaut = true; } else { guy = spawnList[ i ] spawn_ai(); } if ( ( spawn_failed( guy ) ) || ( !isAlive( guy ) ) || ( !isdefined( guy ) ) ) { numberFailedAttempts++ ; continue; } spawnersUsed[ spawnersUsed.size ] = spawnList[ i ]; if ( isjuggernaut ) { level.pmc.juggernauts_spawned++ ; if ( level.pmc.send_in_juggernaut ) { guy thread juggernaut_hunt_immediately_behavior(); level.pmc.send_in_juggernaut = false; } else guy thread [[ level.pmc.set_goal_func ]](); } else { guy init_enemy_combat_mode( i ); guy thread [[ level.pmc.set_goal_func ]](); } guy thread enemy_death_wait(); guy thread enemy_seek_player_wait(); numberToSpawnRemaining -- ; assert( numberToSpawnRemaining >= 0 ); if ( numberToSpawnRemaining == 0 ) break; } if ( removeSpawnedFromArray ) { // remove the guys that spawned from the spawner list so they don't get spawned twice enemy_spawner_count_before = level.pmc.enemy_spawners.size; level.pmc.enemy_spawners = array_exclude( level.pmc.enemy_spawners, spawnersUsed ); assert( level.pmc.enemy_spawners.size == enemy_spawner_count_before - spawnersUsed.size ); } return spawnersUsed.size; } enemy_update_goal_on_jumpout() { self endon( "death" ); self waittill( "jumpedout" ); waittillframeend; self [[ level.pmc.set_goal_func ]](); } enemy_set_goal_when_player_spotted() { self endon( "death" ); if ( !isAI( self ) ) return; if ( !isAlive( self ) ) return; //small goal, but aware of player moves them to spots where they could see him, but keeps them spread out self.goalradius = 450; self set_goal_height(); if ( isdefined( self.juggernaut ) ) { self wait_for_notify_or_timeout( "enemy_visible", JUGGERNAUT_ENEMY_VISIBLE_TIMEOUT ); juggernaut_set_goal_when_player_spotted_loop(); return; } self waittill( "enemy_visible" ); if ( self animscripts\combat_utility::isShotgunAI() || ( randomint( 3 ) == 0 ) ) enemy_set_goal_when_player_spotted_loop(); } set_goal_height() { if ( isdefined( self.juggernaut ) ) self.goalheight = DEFAULT_ENEMY_GOAL_HEIGHT_JUGGERNAUT; else if ( self animscripts\combat_utility::isSniper() ) self.goalheight = DEFAULT_ENEMY_GOAL_HEIGHT_SNIPER; else self.goalheight = DEFAULT_ENEMY_GOAL_HEIGHT; } juggernaut_set_goal_when_player_spotted_loop() { self endon( "death" ); self.useChokePoints = false; //small goal at the player so they can close in aggressively while ( 1 ) { self.goalradius = 32; self set_goal_height(); if ( isdefined( self.enemy ) ) self setgoalpos( self.enemy.origin ); else self setgoalpos( level.player.origin ); wait 4; } } enemy_set_goal_when_player_spotted_loop() { self endon( "death" ); //large goal at the player so they can close in intelligently while ( 1 ) { if ( self.doingAmbush ) self.goalradius = 2048; else if ( self animscripts\combat_utility::isSniper() ) self.goalradius = 5000; else self.goalradius = randomintrange( 1200, 1600 ); if ( isdefined( self.enemy ) ) self setgoalpos( self.enemy.origin ); else self setgoalpos( level.player.origin ); wait 45; } } enemy_set_goalradius() { if ( !isAI( self ) ) return; if ( !isAlive( self ) ) return; self.goalradius = randomintrange( level.pmc.enemy_goal_radius_min, level.pmc.enemy_goal_radius_max ); self set_goal_height(); } enemy_seek_player_wait() { self endon( "death" ); flag_wait( "enemies_seek_players" ); debug_print( "AI is seeking out player!" ); self enemy_seek_player(); } enemy_seek_player( modScale ) { self endon( "death" ); self.accuracy = 50;// final enemies are more deadly self.combatMode = "cover"; while ( 1 ) { self.goalradius = randomintrange( 1200, 1600 ); self set_goal_height(); if ( isdefined( self.enemy ) && self.enemy.classname == "player" ) self setgoalpos( self.enemy.origin ); else self setgoalpos( level.players[ randomint( level.players.size ) ].origin ); wait 45; } } enemy_seek_objective_in_stages() { self endon( "death" ); self.goalradius = 1600; self setGoalPos( level.pmc.defend_obj_origin ); wait 45; self.goalradius = 1100; wait 45; self.goalradius = 600; } enemy_seek_player_in_stages() { self endon( "death" ); modScale = 3; for ( ;; ) { self.goalradius = randomintrange( ENEMY_GOAL_RADIUS_SEEK_PLAYER_MIN, ENEMY_GOAL_RADIUS_SEEK_PLAYER_MAX ) * modScale; self set_goal_height(); self setGoalEntity( random( level.players ) ); modScale -- ; if ( modScale <= 0 ) break; wait 45; } } enemy_death_wait() { self thread enemy_wait_death(); self thread enemy_wait_damagenotdone(); } enemy_wait_death() { self endon( "cancel_enemy_death_wait" ); self waittill( "death", attacker ); thread enemy_died( attacker ); self notify( "cancel_enemy_death_wait" ); } enemy_wait_damagenotdone() { self endon( "cancel_enemy_death_wait" ); self waittill( "damage_notdone", damage, attacker ); thread enemy_died( attacker ); self notify( "cancel_enemy_death_wait" ); } enemy_died( attacker ) { if ( flag_exist( "special_op_terminated" ) && flag( "special_op_terminated" ) ) { return; } if ( level.pmc.limitRespawns ) { level.pmc.enemies_remaining -- ; } assert( level.pmc.enemies_remaining >= 0 ); level notify( "enemy_died" );// needed for pacing level notify( "update_enemies_remaining_count" ); thread re_populate_enemies(); // Check if we should send the remaining few enemies out towards the AI if ( level.pmc.enemies_remaining <= SEEK_PLAYERS_ENEMY_COUNT ) flag_set( "enemies_seek_players" ); // Check to see if the mission has been completed if ( ( level.pmc.limitRespawns ) && ( level.pmc.enemies_remaining == 0 ) ) { if ( isObjectiveMatch() ) flag_wait( "objective_complete" ); // wait 3.0; if ( isdefined( level.pmc.objective_enemies_index ) ) objective_state( level.pmc.objective_enemies_index, "done" ); if ( !isObjectiveMatch() ) thread mission_complete(); } } isEliminationMatch() { return( level.pmc_gametype == "mode_elimination" ); } isObjectiveMatch() { return( level.pmc_gametype == "mode_objective" ); } isDefendMatch() { return( level.pmc_gametype == "mode_defend" ); } setup_objective_entities() { objectiveLocations = []; objectiveEnt = getentarray( "pmc_objective", "targetname" ); objectiveEnt = get_array_of_closest( getAveragePlayerOrigin(), objectiveEnt ); foreach( i, ent in objectiveEnt ) { objectiveLocations[ i ] = spawnStruct(); objectiveLocations[ i ].laptop = ent; assert( isdefined( ent.target ) ); objectiveLocations[ i ].trigger = getent( ent.target, "targetname" ); assert( isdefined( objectiveLocations[ i ].trigger ) ); assert( objectiveLocations[ i ].trigger.classname == "trigger_use" ); objectiveLocations[ i ].laptop hide(); objectiveLocations[ i ].trigger trigger_off(); } if ( isDefendMatch() ) { //find closest volume defend_volumes = getentarray( "info_volume_pmcDefend", "classname" ); defend_volume = getClosest( getAveragePlayerOrigin(), defend_volumes ); //find the obj closest to that volume obj_index = get_closest_index( defend_volume.origin, objectiveEnt ); defend_obj = objectiveLocations[ obj_index ]; objectiveLocations = array_remove( objectiveLocations, defend_obj ); level.pmc.defend_obj_origin = defend_obj.laptop.origin; assertEx( isdefined( level.pmc.defend_obj_origin ), "Undefined defend location origin." ); thread set_up_defend_location( defend_obj, defend_volume ); } // If we're in an objective match, set the objective variable to a random location if ( isObjectiveMatch() ) { randomLocation = randomint( objectiveLocations.size ); level.pmc.objective = objectiveLocations[ randomLocation ]; level.pmc.objective.laptop_obj = spawn( "script_model", level.pmc.objective.laptop.origin ); level.pmc.objective.laptop_obj.angles = level.pmc.objective.laptop.angles; level.pmc.objective.laptop_obj setModel( "com_laptop_2_open_obj" ); objectiveLocations = array_remove( objectiveLocations, objectiveLocations[ randomLocation ] ); } // Delete unused objective location entities foreach( location in objectiveLocations ) location.trigger delete(); } delete_sentry_pickup() { waittillframeend; self thread common_scripts\_sentry::delete_sentry_turret(); } set_up_defend_location( defend_obj, defend_volume ) { defend_volume thread defend_think( 80 ); thread defend_setup_time_think( defend_obj ); foreach ( gun in level.sentry_pickups ) { d = distance( gun.origin, defend_obj.trigger.origin ); if ( d <= 300 ) continue; gun thread delete_sentry_pickup(); } } defend_setup_time_think( defend_obj ) { defend_obj.trigger thread defend_setup_time_trigger(); thread defend_setup_time_hint(); wait level.pmc.defendSetupTime; flag_set( "pmc_defend_setup_time_finished" ); defend_obj.trigger trigger_off(); } defend_setup_time_hint() { level endon( "pmc_defend_setup_time_finished" ); wait 15; hint_defend_setup = spawnstruct(); // Approach the laptop to skip set up time. hint_defend_setup.string = &"PMC_START_ATTACK_HINT"; hint_defend_setup.timeout = 5; foreach ( player in level.players ) player show_hint( hint_defend_setup ); flag_wait( "pmc_defend_setup_time_finished" ); hide_hint( hint_defend_setup ); } defend_setup_time_trigger() { // Press and hold &&1 on the laptop to skip set up time. self setHintString( &"PMC_START_ATTACK_USE_HINT" ); self waittill( "trigger" ); flag_set( "pmc_defend_setup_time_finished" ); self trigger_off(); } defend_think( fill_time ) { totalTime = fill_time; enemy_time = 0; bar_doesnt_exist = true; bar = undefined; while ( 1 ) { //reset and check enemies in volume each frame //also pull nearby enemies into volume self.enemy_count = 0; enemies = getaiarray( "axis" ); foreach ( enemy in enemies ) { if ( ( distance( enemy.origin, self.origin ) ) < 600 ) { enemy setGoalPos( level.pmc.defend_obj_origin ); if ( enemy istouching( self ) ) self.enemy_count++ ; } } //enemies are in volume, add and update bar if ( self.enemy_count > 0 ) { enemy_time = enemy_time + ( self.enemy_count * .05 ); if ( enemy_time > 0 ) { if ( bar_doesnt_exist ) { bar = createBar(); bar setPoint( "CENTER", undefined, 0, 75 ); bar.text = createFontString( "objective", 1.4 ); bar.text setPoint( "CENTER", undefined, 0, 63 ); // HQ Damage bar.text setText( &"PMC_HQ_DAMAGE" ); bar.text.sort = -1; bar_doesnt_exist = false; bar updateBar( enemy_time / totalTime ); } else { bar updateBar( enemy_time / totalTime ); } } } else// no enemies in volume, if players are, empty the bar { foreach ( player in level.players ) if ( player istouching( self ) ) enemy_time = enemy_time - .1; if ( enemy_time < 0 ) enemy_time = 0; if ( bar_doesnt_exist == false ) bar updateBar( enemy_time / totalTime ); } if ( ( enemy_time == 0 ) && ( bar_doesnt_exist == false ) ) { bar.text notify( "destroying" ); bar.text destroyElem(); bar notify( "destroying" ); bar destroyElem(); bar_doesnt_exist = true; } if ( enemy_time >= fill_time ) { // Keep enemies away from the objective! setDvar( "ui_deadquote", &"PMC_DEFEND_FAILED" ); maps\_utility::missionFailedWrapper(); } wait .05; } } show_remaining_enemy_count() { /* "default" "bigfixed" "smallfixed" "objective" "big" "small" "hudbig" "hudsmall" */ /* level.pmc.hud.remainingEnemyCountHudElem = newHudElem(); level.pmc.hud.remainingEnemyCountHudElem.x = -10; level.pmc.hud.remainingEnemyCountHudElem.y = -100; level.pmc.hud.remainingEnemyCountHudElem.font = "hudsmall"; level.pmc.hud.remainingEnemyCountHudElem.fontscale = 1.0; level.pmc.hud.remainingEnemyCountHudElem.alignX = "right"; level.pmc.hud.remainingEnemyCountHudElem.alignY = "bottom"; level.pmc.hud.remainingEnemyCountHudElem.horzAlign = "right"; level.pmc.hud.remainingEnemyCountHudElem.vertAlign = "bottom"; // Enemies remaining: &&1 level.pmc.hud.remainingEnemyCountHudElem.label = &"PMC_ENEMIES_REMAINING"; level.pmc.hud.remainingEnemyCountHudElem.alpha = 1;*/ self.remainingEnemyCountHudelem = so_create_hud_item( 2, so_hud_ypos(), &"SPECIAL_OPS_HOSTILES", self ); self.remainingEnemyCountHudelemNum = so_create_hud_item( 2, so_hud_ypos(), "", self ); self.remainingEnemyCountHudelemNum.alignX = "left"; /* self thread info_hud_handle_fade( self.remainingEnemyCountHudelem, "mission_complete" ); self thread info_hud_handle_fade( self.remainingEnemyCountHudelemNum, "mission_complete" );*/ for ( ;; ) { //thread enemy_remaining_count_blimp(); // Update the number of enemies remaining on the HUD self.remainingEnemyCountHudElemNum setValue( level.pmc.enemies_remaining ); if ( isdefined( level.pmc.enemies_kills_to_win ) && ( level.pmc.enemies_kills_to_win > 0 ) ) thread so_dialog_counter_update( level.pmc.enemies_remaining, level.pmc_enemies ); // Kill all enemies in the level [ &&1 Remaining ]. if ( isdefined( level.pmc.objective_enemies_index ) ) // Kill all enemies in the level [ &&1 Remaining ]. objective_String_NoMessage( level.pmc.objective_enemies_index, &"PMC_OBJECTIVE_KILL_ENEMIES_REMAINING", level.pmc.enemies_remaining ); if ( level.pmc.enemies_remaining <= 0 ) { self.remainingEnemyCountHudelem thread so_hud_pulse_success(); self.remainingEnemyCountHudelemNum thread so_hud_pulse_success(); break; } if ( IsDefined( level.pmc_low_enemy_count ) && level.pmc.enemies_remaining <= level.pmc_low_enemy_count ) { self.remainingEnemyCountHudelem thread so_hud_pulse_close(); self.remainingEnemyCountHudelemNum thread so_hud_pulse_close(); } level waittill( "update_enemies_remaining_count" ); } flag_wait( "mission_complete" ); self.remainingEnemyCountHudelem thread so_remove_hud_item(); self.remainingEnemyCountHudelemNum thread so_remove_hud_item(); } enemy_remaining_count_blimp() { level notify( "enemy_remaining_count_blimp" ); level endon( "enemy_remaining_count_blimp" ); scaleTimeOut = 0.1; scaleTimeIn = 0.4; scaleSize = 1.3; wait 0.1; level.pmc.hud.remainingEnemyCountHudElem changeFontScaleOverTime( scaleTimeOut ); level.pmc.hud.remainingEnemyCountHudElem.fontscale = scaleSize; wait scaleTimeOut + 0.2; level.pmc.hud.remainingEnemyCountHudElem changeFontScaleOverTime( scaleTimeIn ); level.pmc.hud.remainingEnemyCountHudElem.fontscale = 1.0; } gametype_setup() { if ( isObjectiveMatch() ) { array_thread( level.players, ::player_use_objective_think ); thread wait_objective_complete(); } thread enable_challenge_timer( "mission_start", "mission_complete" ); } player_use_objective_think() { level endon( "kill_objective_use_thread" ); while ( !isdefined( level.pmc.objective ) ) wait 0.05; // Press and hold &&1 to use the laptop. level.pmc.objective.trigger trigger_on(); level.pmc.objective.laptop show(); level.pmc.objective.trigger.active = true; level.pmc.objective.trigger sethintstring( &"PMC_HINT_USELAPTOP" ); for ( ;; ) { wait 0.05; level.pmc.objective.trigger waittill( "trigger", player ); if ( player != self ) continue; buttonTime = 0; totalTime = 3.0; qDone = false; player thread freeze_controls_while_using_laptop(); self.objective_bar = self createClientProgressBar( self, 60 ); self.objective_bar_text = self createClientFontString( "default", 1.2 ); self.objective_bar_text setPoint( "CENTER", undefined, 0, 45 ); self.objective_bar_text settext( &"PMC_HQ_RECOVERING_INTEL" ); // Retrieving Intel... while ( ( self useButtonPressed() ) && ( !flag( "objective_complete" ) ) ) { self.objective_bar updateBar( buttonTime / totalTime ); wait 0.05; buttonTime += 0.05; if ( buttonTime > totalTime ) { qDone = true; break; } } if ( isdefined( self.objective_bar ) ) self.objective_bar destroyElem(); if ( isdefined( self.objective_bar_text ) ) self.objective_bar_text destroyElem(); player notify( "remove_laptop_pickup_hud" ); if ( qDone ) { player PlaySound( "intelligence_pickup" ); break; } } // Remove progress bars from all players that might have been using the objective at the same time foreach ( player in level.players ) { player notify( "remove_laptop_pickup_hud" ); if ( isdefined( player.objective_bar ) ) { player.objective_bar destroyElem(); player.objective_bar = undefined; } if ( isdefined( player.objective_bar_text ) ) { player.objective_bar_text destroyElem(); player.objective_bar_text = undefined; } } level.pmc.objective.trigger delete(); level.pmc.objective.laptop_obj delete(); level.pmc.objective.laptop delete(); flag_set( "objective_complete" ); } freeze_controls_while_using_laptop() { self endon( "death" ); self disableweapons(); self freezeControls( true ); self waittill( "remove_laptop_pickup_hud" ); self enableweapons(); self freezeControls( false ); } wait_objective_complete() { flag_wait( "objective_complete" ); wait 0.05; level notify( "kill_objective_use_thread" ); extraction_info = get_extraction_location(); // Objective was completed, now get to the extraction zone objective_state( 1, "done" ); // Reach the extraction zone before time runs out. objective_add( 2, "current", &"PMC_OBJECTIVE_EXTRACT", extraction_info.script_origin.origin ); if ( !flag( "exfiltrate_music_playing" ) ) { flag_set( "exfiltrate_music_playing" ); // thread musicPlayWrapper( level.pmc.music[ "exfiltrate" ] ); } thread play_local_sound( "exfiltrate" ); playFX( getfx( "extraction_smoke" ), extraction_info.script_origin.origin ); // Wait until all alive players are in the extraction zone // Wait for all players to be inside extraction zone maps\_specialops_code::wait_all_players_are_touching( extraction_info.trigger ); // Complete the objective objective_state( 2, "done" ); flag_set( "extraction_complete" ); flag_set( "mission_complete" ); thread mission_complete(); } get_extraction_location() { extraction_info = spawnStruct(); // Get all available extraction locations in the map extraction_origins = getentarray( "extraction", "targetname" ); averageOrigin = getAveragePlayerOrigin(); extraction_origins = get_array_of_closest( averageOrigin, extraction_origins ); // Choose the far extraction point from where the players are extraction_info.script_origin = extraction_origins[ extraction_origins.size - 1 ]; assert( isdefined( extraction_info.script_origin ) ); extraction_info.trigger = getent( extraction_info.script_origin.target, "targetname" ); assert( isdefined( extraction_info.trigger ) ); return extraction_info; } add_player_objectives() { if ( isObjectiveMatch() ) { objective_add_laptop( 1 ); } else if ( isDefendMatch() ) { thread objective_add_defend(); } else { objective_add_enemies( 1 ); foreach ( player in level.players ) player thread show_remaining_enemy_count(); } } objective_add_enemies( objNum ) { if ( !isdefined( objNum ) ) objNum = 1; level.pmc.objective_enemies_index = objNum; // Kill all enemies in the level. objective_add( objNum, "current", &"PMC_OBJECTIVE_KILL_ENEMIES", ( 0, 0, 0 ) ); // Kill all enemies in the level [ &&1 Remaining ]. objective_String_NoMessage( objNum, &"PMC_OBJECTIVE_KILL_ENEMIES_REMAINING", level.pmc.enemies_remaining ); } objective_add_laptop( objNum ) { if ( !isdefined( objNum ) ) objNum = 1; assert( isdefined( level.pmc.objective.trigger.origin ) ); //wait for trigger to get turned on since it effects it's origin while( !isdefined( level.pmc.objective.trigger.active ) ) wait 0.05; // Retrieve enemy intel. objective_add( objNum, "current", &"PMC_OBJECTIVE_ABORT_CODES", level.pmc.objective.trigger.origin ); } objective_add_defend() { assert( isdefined( level.pmc.defendSetupTime ) ); assert( isdefined( level.pmc.defendTime ) ); // Set up a defensive position before enemy attack. objective_add( 1, "current", &"PMC_OBJECTIVE_SETUP_DEFENSES", ( 0, 0, 0 ) ); thread show_defend_timer(); flag_wait( "pmc_defend_setup_time_finished" ); //wait level.pmc.defendSetupTime; flag_set( "defend_started" ); objective_state( 1, "done" ); // Survive until time runs out. objective_add( 2, "current", &"PMC_OBJECTIVE_DEFEND", ( 0, 0, 0 ) ); wait level.pmc.defendTime; objective_state( 2, "done" ); thread mission_complete(); } show_defend_timer() { level.pmc.hud.defendTimer = newHudElem(); level.pmc.hud.defendTimer.x = 0; level.pmc.hud.defendTimer.y = 30; level.pmc.hud.defendTimer.fontScale = 2.5; level.pmc.hud.defendTimer.alignX = "left"; level.pmc.hud.defendTimer.alignY = "middle"; level.pmc.hud.defendTimer.horzAlign = "left"; level.pmc.hud.defendTimer.vertAlign = "middle"; level.pmc.hud.defendTimer.alpha = 1; // Time until attack: &&1 level.pmc.hud.defendTimer.label = &"PMC_TIME_UNTIL_ATTACK"; level.pmc.hud.defendTimer setTimer( level.pmc.defendSetupTime ); flag_wait( "pmc_defend_setup_time_finished" ); // Time Remaining: &&1 level.pmc.hud.defendTimer.label = &"PMC_TIME_REMAINING"; level.pmc.hud.defendTimer setTimer( level.pmc.defendTime ); } play_local_sound( alias, loopTime, stop_loop_notify ) { assert( isdefined( level.pmc.sound ) ); assert( isdefined( level.pmc.sound[ alias ] ) ); if ( isArray( level.pmc.sound[ alias ] ) ) { rand = randomint( level.pmc.sound[ alias ].size ); aliasToPlay = level.pmc.sound[ alias ][ rand ]; } else { aliasToPlay = level.pmc.sound[ alias ]; } if ( !isdefined( loopTime ) ) { array_thread( level.players, ::playLocalSoundWrapper, aliasToPlay ); return; } level endon( "special_op_terminated" ); level endon( stop_loop_notify ); for ( ;; ) { array_thread( level.players, ::playLocalSoundWrapper, aliasToPlay ); wait loopTime; } } mission_complete() { // thread musicPlayWrapper( level.pmc.music[ "mission_complete" ] ); // wait 1.5; flag_set( "mission_complete" ); } debug_print( string ) { if ( getdvar( "pmc_debug" ) == "1" ) { assert( isdefined( string ) ); iprintln( string ); } } debug_show_enemy_spawners_count() { if ( isDefendMatch() ) return; level.pmc.hud.enemySpawnerCountHudElem = newHudElem(); level.pmc.hud.enemySpawnerCountHudElem.x = 0; level.pmc.hud.enemySpawnerCountHudElem.y = -30; level.pmc.hud.enemySpawnerCountHudElem.fontScale = 1.5; level.pmc.hud.enemySpawnerCountHudElem.alignX = "left"; level.pmc.hud.enemySpawnerCountHudElem.alignY = "bottom"; level.pmc.hud.enemySpawnerCountHudElem.horzAlign = "left"; level.pmc.hud.enemySpawnerCountHudElem.vertAlign = "bottom"; // Enemy Spawners: &&1 level.pmc.hud.enemySpawnerCountHudElem.label = &"PMC_DEBUG_SPAWNER_COUNT"; level.pmc.hud.enemySpawnerCountHudElem.alpha = 1; for ( ;; ) { assert( isdefined( level.pmc.enemy_spawners ) ); assert( isdefined( level.pmc.enemy_spawners.size ) ); level.pmc.hud.enemySpawnerCountHudElem setValue( level.pmc.enemy_spawners.size ); wait 0.05; } } debug_show_enemies_alive_count() { level.pmc.hud.enemyCountHudElem = newHudElem(); level.pmc.hud.enemyCountHudElem.x = 0; level.pmc.hud.enemyCountHudElem.y = -15; level.pmc.hud.enemyCountHudElem.fontScale = 1.5; level.pmc.hud.enemyCountHudElem.alignX = "left"; level.pmc.hud.enemyCountHudElem.alignY = "bottom"; level.pmc.hud.enemyCountHudElem.horzAlign = "left"; level.pmc.hud.enemyCountHudElem.vertAlign = "bottom"; // Enemies Alive: &&1 level.pmc.hud.enemyCountHudElem.label = &"PMC_DEBUG_ENEMY_COUNT"; level.pmc.hud.enemyCountHudElem.alpha = 1; for ( ;; ) { enemyAIAlive = getaiarray( "axis" ); assert( isdefined( enemyAIAlive ) ); assert( isdefined( enemyAIAlive.size ) ); level.pmc.hud.enemyCountHudElem setValue( enemyAIAlive.size + level.pmc.enemy_vehicles_alive ); wait 0.05; } } debug_show_vehicles_alive_count() { level.pmc.hud.enemyVehicleCountHudElem = newHudElem(); level.pmc.hud.enemyVehicleCountHudElem.x = 0; level.pmc.hud.enemyVehicleCountHudElem.y = 0; level.pmc.hud.enemyVehicleCountHudElem.fontScale = 1.5; level.pmc.hud.enemyVehicleCountHudElem.alignX = "left"; level.pmc.hud.enemyVehicleCountHudElem.alignY = "bottom"; level.pmc.hud.enemyVehicleCountHudElem.horzAlign = "left"; level.pmc.hud.enemyVehicleCountHudElem.vertAlign = "bottom"; // Vehicles Alive: &&1 level.pmc.hud.enemyVehicleCountHudElem.label = &"PMC_DEBUG_VEHICLE_COUNT"; level.pmc.hud.enemyVehicleCountHudElem.alpha = 1; for ( ;; ) { level.pmc.hud.enemyVehicleCountHudElem setValue( level.pmc.enemy_vehicles_alive ); wait 0.05; } } /* set_up_preplaced_enemy_sentry_turrets( turrets ) { if ( turrets.size == 0 ) return; //sort from closest to furtherest turrets = get_array_of_closest( getAveragePlayerOrigin(), turrets ); //remove the closest 2 turrets[ 0 ] thread common_scripts\_sentry::delete_sentry_turret(); turrets = array_remove( turrets, turrets[ 0 ] ); if ( turrets.size == 0 ) return; turrets[ 0 ] thread common_scripts\_sentry::delete_sentry_turret(); turrets = array_remove( turrets, turrets[ 0 ] ); num_to_keep = 3; if ( turrets.size <= num_to_keep ) return; turrets = array_randomize( turrets ); for ( i = 0 ; i < turrets.size ; i++ ) { if ( i >= num_to_keep ) { turrets[ i ] thread common_scripts\_sentry::delete_sentry_turret(); } } } */ room_breach_spawned() { // This thread gets ran when a room breach enemy gets spawned via the room breaching global script delete_unseen_enemy(); self thread enemy_death_wait(); } delete_unseen_enemy() { // Deletes 1 far away not-visible enemy in the map // Get array of enemy spawners in order of farthest to closest ai = get_array_of_closest( getAveragePlayerOrigin(), getaiarray( "axis" ) ); ai = array_reverse( ai ); foreach ( enemy in ai ) { if ( !enemy enemy_can_see_any_player() ) { println( "^3An AI was deleted to make room for a door breach spawner" ); enemy notify( "cancel_enemy_death_wait" ); enemy delete(); return; } } } enemy_can_see_any_player() { foreach ( player in level.players ) { if ( self cansee( player ) ) return true; } return false; } staged_pacing_system() { one_third = int( level.pmc.enemies_kills_to_win / 3 ); two_thirds = int( ( level.pmc.enemies_kills_to_win * 2 ) / 3 ); level.pmc.max_juggernauts = int( level.pmc.enemies_kills_to_win / 7 ); thread count_dead_juggernauts(); flag_set( "staged_pacing_used" ); while ( 1 ) { level waittill( "enemy_died" ); //stage 1 one enemy killed, less than 1/3 of enemies killed //attack heli chance if ( level.pmc.enemies_remaining >= two_thirds ) { //50% chance through this entire period println( "pacing stage 1" ); force_odds = one_third * 2; } //Stage 2 1/3 to 2/3rds of enemies killed //transport heli or attack heli //transport isnt forced, just allowed //one juggernaut at a time else if ( level.pmc.enemies_remaining >= one_third ) { flag_set( "pacing_stage_2" ); println( "pacing stage 2" ); thread send_in_one_juggernaut( one_third ); //create_one_heli( if_none_already ); } //Stage 3 more than 2/3rds of enemies killed //two juggernauts at a time //second heli else { flag_set( "pacing_stage_3" ); println( "pacing stage 3" ); //create_one_heli(); thread send_in_multiple_juggernauts(); } } //Stage 4 less than 7 enemies left //beef up remaining and send in //done elsewhere } count_dead_juggernauts() { while ( 1 ) { level waittill( "juggernaut_died" ); level.pmc.juggernauts_killed++ ; } } juggernaut_hunt_immediately_behavior() { self endon( "death" ); self.useChokePoints = false; //small goal at the player so they can close in aggressively while ( 1 ) { self.goalradius = 32; self set_goal_height(); if ( isdefined( self.enemy ) ) self setgoalpos( self.enemy.origin ); else { enemyPlayer = level.players[ randomInt( level.players.size ) ]; self setgoalpos( enemyPlayer.origin ); } wait 4; } } send_in_one_juggernaut( one_third ) { //this doesnt actually spawn or force the juggernaut //it just alters the checks in should_spawn_juggernaut() //and removes a guy to make room living = level.pmc.juggernauts_spawned - level.pmc.juggernauts_killed; allowed_for_this_stage = ( level.pmc.max_juggernauts / 2 ); if ( living > 0 ) return; if ( level.pmc.juggernauts_spawned >= allowed_for_this_stage ) return; println( "pacing trying for 1 juggernaut" ); odds = int( ( one_third / allowed_for_this_stage ) / 2 ); if ( randomint( odds ) > 0 ) return; println( "pacing spawning 1 juggernaut" ); delete_unseen_enemy(); level.pmc.send_in_juggernaut = true; } send_in_multiple_juggernauts() { jugs_remaining = level.pmc.max_juggernauts - level.pmc.juggernauts_spawned; if ( jugs_remaining < 1 ) return; println( "pacing trying for x juggernauts" ); odds = int( level.pmc.enemies_remaining / jugs_remaining ); if ( odds <= 0 ) odds = 1; if ( randomint( odds ) > 0 ) return; println( "pacing spawning 1 juggernaut" ); delete_unseen_enemy(); level.pmc.send_in_juggernaut = true; } should_spawn_juggernaut() { if ( level.pmc_alljuggernauts ) return true; if ( !level.juggernaut_mode ) return false; // spawn a juggernaut at the start of the game instead of waiting until the end if this is set. if ( !level.pmc.spawned_juggernaut_at_game_start ) { assert( level.pmc.spawned_juggernaut_at_game_start_counter > 0 ); chance = randomint( level.pmc.spawned_juggernaut_at_game_start_counter ); level.pmc.spawned_juggernaut_at_game_start_counter--; if ( chance == 0 ) { level.pmc.spawned_juggernaut_at_game_start = true; return true; } } if ( flag( "staged_pacing_used" ) ) { if ( level.pmc.send_in_juggernaut ) return true; else return false; } if ( randomint( 10 ) > 0 ) return false; else return true; }