1939 lines
55 KiB
Plaintext
1939 lines
55 KiB
Plaintext
|
#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;
|
||
|
}
|