2955 lines
90 KiB
Plaintext
2955 lines
90 KiB
Plaintext
#include common_scripts\utility;
|
|
#include maps\mp\alien\_utility;
|
|
|
|
CYCLE_TABLE = "mp/alien/default_cycle_spawn.csv"; // cycle data tablelookup
|
|
CYCLE_TABLE_HARDCORE = "mp/alien/default_cycle_spawn_hardcore.csv";
|
|
TABLE_CYCLE_DEF_INDEX = 399; // max index used for cycle definition
|
|
|
|
TABLE_INDEX = 0; // Indexing
|
|
TABLE_CYCLE = 1; // cycle number
|
|
TABLE_INTENSITY = 2; // Cycle intensity level
|
|
TABLE_INTENSITY_THRESHOLD = 3; // Intensity value to switch to this level
|
|
TABLE_RESPAWN_THRESHOLD = 4; // Threshold to start respawning at
|
|
TABLE_RESPAWN_DELAY = 5; // Delay after hitting threshold before respawning starts
|
|
TABLE_LANES = 6; // Lanes to use for this wave
|
|
TABLE_TYPES1 = 7; // First AI Type
|
|
TABLE_COUNT1 = 8; // First AI Count
|
|
TABLE_TYPES2 = 9; // Second AI Type
|
|
TABLE_COUNT2 = 10; // Second AI Count
|
|
TABLE_TYPES3 = 11; // Third AI Type
|
|
TABLE_COUNT3 = 12; // Third AI Count
|
|
TABLE_TYPES4 = 13; // Fourth AI Type
|
|
TABLE_COUNT4 = 14; // Fourth AI Count
|
|
TABLE_TYPES5 = 15; // Fifth AI Type
|
|
TABLE_COUNT5 = 16; // Fifth AI Count
|
|
TABLE_TYPES6 = 17; // Sixth AI Type
|
|
TABLE_COUNT6 = 18; // Sixth AI Count
|
|
TABLE_TYPES7 = 19; // Seventh AI Type
|
|
TABLE_COUNT7 = 20; // Seventh AI Count
|
|
TABLE_TYPES8 = 21; // Eighth AI Type
|
|
TABLE_COUNT8 = 22; // Eighth AI Count
|
|
TABLE_TYPES9 = 23; // Ninth AI Type
|
|
TABLE_COUNT9 = 24; // Ninth AI Count
|
|
|
|
TABLE_SPAWN_EVENT_START_INDEX = 2000;
|
|
TABLE_SPAWN_EVENT_END_INDEX = 2099;
|
|
TABLE_SPAWN_EVENT_CYCLE = 1; // The cycle this spawn event can happen in
|
|
TABLE_SPAWN_EVENT_NOTIFY = 2; // The notify that activates this spawn event
|
|
TABLE_SPAWN_EVENT_ID = 3; // The list of random id for this spawn event
|
|
TABLE_SPAWN_EVENT_TIME_LIMIT = 4; // The time limit before this spawn event expires if not completed
|
|
TABLE_SPAWN_EVENT_LANES = 5; // The lanes to spawn from for this event
|
|
|
|
TABLE_SPAWN_EVENT_WAVE_START_INDEX = 3000;
|
|
TABLE_SPAWN_EVENT_WAVE_END_INDEX = 3099;
|
|
TABLE_SPAWN_EVENT_WAVE_ID = 1; // The unique id this wave belongs to
|
|
TABLE_SPAWN_EVENT_WAVE_BLOCKING = 2; // Whether or not this wave blocks to wait for all members of it to be killed
|
|
TABLE_SPAWN_EVENT_WAVE_SPAWN_DELAY = 3; // The delay before activation after previous wave ends
|
|
TABLE_SPAWN_EVENT_WAVE_TYPE1 = 4; // The first type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_COUNT1 = 5; // The number of first type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_TYPE2 = 6; // The second type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_COUNT2 = 7; // The number of second type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_TYPE3 = 8; // The third type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_COUNT3 = 9; // The number of third type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_TYPE4 = 10; // The fourth type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_COUNT4 = 11; // The number of fourth type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_TYPE5 = 12; // The fifth type to spawn
|
|
TABLE_SPAWN_EVENT_WAVE_COUNT5 = 13; // The number of fifth type to spawn
|
|
|
|
TABLE_MIN_SPAWN_INTERVAL = 400; // Min interval between spawns
|
|
TABLE_SAFE_SPAWN_DISTANCE = 401; // How close player has to be before doing a trace
|
|
TABLE_SPAWN_POINT_LAST_USED_DURATION = 402; // How long a spawn point is considered recently used
|
|
TABLE_GROUP_BREAK_AWAY_DISTANCE = 403; // Min distance before a player is considered to have broken away from team
|
|
TABLE_PLAYER_IN_ZONE_SCORE = 404; // How much to add to zone score for each player in the zone
|
|
TABLE_CURRENT_ATTACKER_ZONE_SCORE = 405; // How much to subtract from zone score for each current attacker on a player in the zone
|
|
TABLE_RECENTLY_SPAWNED_ZONE_MODIFIER = 406; // How much to subtract from zone score for each recently used spawn node in the zone
|
|
TABLE_MAX_BREAK_AWAY_SCORE_INCREASE = 407; // Max amount to increase zone score for a break away player in that zone
|
|
TABLE_SPAWN_EVENT_MIN_ACTIVATION_TIME = 408; // Base time to wait after notify received before activating a spawn event
|
|
TABLE_SPAWN_EVENT_PER_ALIEN_ACTIVATION_INCREASE = 409; // Additional time per alive alien to add to wait time
|
|
TABLE_SPAWN_EVENT_MAX_ACTIVATION_TIME = 410; // Max time to wait before processing a spawn event
|
|
TABLE_VARIABLE_COLUMN = 2; // column for variable values
|
|
|
|
TABLE_CYCLE_SCALAR_START_INDEX = 500;
|
|
TABLE_CYCLE_SCALAR_END_INDEX = 599;
|
|
TABLE_CYCLE_SCALAR_CYCLE = 1;
|
|
TABLE_CYCLE_SCALAR_HEALTH = 2;
|
|
TABLE_CYCLE_SCALAR_DAMAGE = 3;
|
|
TABLE_CYCLE_SCALAR_REWARD = 4;
|
|
TABLE_CYCLE_SCALAR_SCORE = 5;
|
|
|
|
TABLE_CYCLE_DRILL_LAYER_START_INDEX = 600;
|
|
TABLE_CYCLE_DRILL_LAYER_END_INDEX = 699;
|
|
TABLE_CYCLE_DRILL_LAYER_CYCLE = 1;
|
|
TABLE_CYCLE_DRILL_LAYERS = 2;
|
|
TABLE_CYCLE_DRILL_DELAY = 3;
|
|
|
|
TABLE_LANES_START_INDEX = 700;
|
|
TABLE_LANES_END_INDEX = 799;
|
|
TABLE_LANES_ENCOUNTER = 1;
|
|
TABLE_LANES_ACTIVATION_TIME = 2;
|
|
TABLE_LANES_NAMES = 3;
|
|
|
|
TABLE_1000_CYCLE_START_IDX = 1000;
|
|
TABLE_1000_CYCLE_END_IDX = 1999;
|
|
|
|
TABLE_RANDOM_HIVES_START_INDEX = 900;
|
|
TABLE_RANDOM_HIVES_END_INDEX = 999;
|
|
TABLE_RANDOM_HIVES_NUM_SPAWNED = 1;
|
|
TABLE_RANDOM_HIVES_HIVE_LIST = 2;
|
|
|
|
SPAWN_NODE_INFO_TABLE = "mp/alien/spawn_node_info.csv"; // spawn node info lookup table
|
|
|
|
// table column
|
|
TABLE_SPAWN_NODE_KEY = 1; // The unique identifier specified on the spawn node
|
|
TABLE_SPAWN_VALID_ALIEN_TYPE = 2; // The type of aliens that are allowed to be spawned on this node
|
|
TABLE_ONE_OFF_SCRIPTABLE = 3; // The associated scriptable will be played only once
|
|
TABLE_GOON_VIGNETTE_STATE = 4; // Goon: Intro vignette anim state
|
|
TABLE_GOON_VIGNETTE_INDEX_ARRAY = 5; // Goon: Intro vignette anim index array
|
|
TABLE_GOON_VIGNETTE_LABEL = 6; // Goon: Intro vignette anim label
|
|
TABLE_GOON_VIGNETTE_END_NOTETRACK = 7; // Goon: Intro vignette anim end notetrack
|
|
TABLE_GOON_VIGNETTE_FX = 8; // Goon: Intro vignette FX (triggered by animation notetrack)
|
|
TABLE_GOON_VIGNETTE_SCRIPTABLE = 9; // Goon: Intro vignette scriptable targetname (triggered by animation notetrack)
|
|
TABLE_GOON_VIGNETTE_SCRIPTABLE_STATE = 10; // Goon: Intro vignette scriptable state (triggered by animation notetrack)
|
|
TABLE_BRUTE_VIGNETTE_STATE = 11; // Brute: Intro vignette anim state
|
|
TABLE_BRUTE_VIGNETTE_INDEX_ARRAY = 12; // Brute: Intro vignette anim index array
|
|
TABLE_BRUTE_VIGNETTE_LABEL = 13; // Brute: Intro vignette anim label
|
|
TABLE_BRUTE_VIGNETTE_END_NOTETRACK = 14; // Brute: Intro vignette anim end notetrack
|
|
TABLE_BRUTE_VIGNETTE_FX = 15; // Brute: Intro vignette FX (triggered by animation notetrack)
|
|
TABLE_BRUTE_VIGNETTE_SCRIPTABLE = 16; // Brute: Intro vignette scriptable targetname (triggered by animation notetrack)
|
|
TABLE_BRUTE_VIGNETTE_SCRIPTABLE_STATE = 17; // Brute: Intro vignette scriptable state (triggered by animation notetrack)
|
|
TABLE_SPITTER_VIGNETTE_STATE = 18; // Spitter: Intro vignette anim state
|
|
TABLE_SPITTER_VIGNETTE_INDEX_ARRAY = 19; // Spitter: Intro vignette anim index array
|
|
TABLE_SPITTER_VIGNETTE_LABEL = 20; // Spitter: Intro vignette anim label
|
|
TABLE_SPITTER_VIGNETTE_END_NOTETRACK = 21; // Spitter: Intro vignette anim end notetrack
|
|
TABLE_SPITTER_VIGNETTE_FX = 22; // Spitter: Intro vignette FX (triggered by animation notetrack)
|
|
TABLE_SPITTER_VIGNETTE_SCRIPTABLE = 23; // Spitter: Intro vignette scriptable targetname (triggered by animation notetrack)
|
|
TABLE_SPITTER_VIGNETTE_SCRIPTABLE_STATE = 24; // Spitter: Intro vignette scriptable state (triggered by animation notetrack)
|
|
TABLE_ELITE_VIGNETTE_STATE = 25; // Elite: Intro vignette anim state
|
|
TABLE_ELITE_VIGNETTE_INDEX_ARRAY = 26; // Elite: Intro vignette anim index array
|
|
TABLE_ELITE_VIGNETTE_LABEL = 27; // Elite: Intro vignette anim label
|
|
TABLE_ELITE_VIGNETTE_END_NOTETRACK = 28; // Elite: Intro vignette anim end notetrack
|
|
TABLE_ELITE_VIGNETTE_FX = 29; // Elite: Intro vignette FX (triggered by animation notetrack)
|
|
TABLE_ELITE_VIGNETTE_SCRIPTABLE = 30; // Elite: Intro vignette scriptable targetname (triggered by animation notetrack)
|
|
TABLE_ELITE_VIGNETTE_SCRIPTABLE_STATE = 31; // Elite: Intro vignette scriptable state (triggered by animation notetrack)
|
|
TABLE_MINION_VIGNETTE_STATE = 32; // Minion: Intro vignette anim state
|
|
TABLE_MINION_VIGNETTE_INDEX_ARRAY = 33; // Minion: Intro vignette anim index array
|
|
TABLE_MINION_VIGNETTE_LABEL = 34; // Minion: Intro vignette anim label
|
|
TABLE_MINION_VIGNETTE_END_NOTETRACK = 35; // Minion: Intro vignette anim end notetrack
|
|
TABLE_MINION_VIGNETTE_FX = 36; // Minion: Intro vignette FX (triggered by animation notetrack)
|
|
TABLE_MINION_VIGNETTE_SCRIPTABLE = 37; // Minion: Intro vignette scriptable targetname (triggered by animation notetrack)
|
|
TABLE_MINION_VIGNETTE_SCRIPTABLE_STATE = 38; // Minion: Intro vignette scriptable state (triggered by animation notetrack)
|
|
|
|
TABLE_SPAWN_NODE_INFO_START_INDEX = 1;
|
|
TABLE_SPAWN_NODE_INFO_MAX_INDEX = 100;
|
|
|
|
BASE_PLAYER_COUNT_MULTIPLIER =0.49;
|
|
ADDITIONAL_PLAYER_COUNT_MULTIPLIER = 0.17;
|
|
MAX_ALIEN_COUNT = 18;
|
|
|
|
|
|
MIN_SPAWN_INTERVAL = 1.0;
|
|
SAFE_SPAWN_DISTANCE = 500.0;
|
|
LAST_USED_TIME_DURATION = 5000.0;
|
|
|
|
GROUP_BREAK_AWAY_DISTANCE = 1500;
|
|
PLAYER_IN_ZONE_SCORE = 0.75;
|
|
CURRENT_ATTACKER_FOR_ZONE_SCALE = 0.25;
|
|
RECENTLY_SPAWNED_ZONE_MODIFIER = 0.25;
|
|
MAX_BREAK_AWAY_SCORE_INCREASE = 2.5;
|
|
|
|
LANE_CHANGE_SPAWN_SOUND = "alien_distant";
|
|
|
|
init()
|
|
{
|
|
//Cycle Table setup based on difficulty hardcore or normal
|
|
|
|
if ( !isdefined( level.alien_cycle_table ) )
|
|
level.alien_cycle_table = CYCLE_TABLE;
|
|
|
|
//hardcore cycle table
|
|
if ( !isdefined( level.alien_cycle_table_hardcore ) )
|
|
level.alien_cycle_table_hardcore = CYCLE_TABLE_HARDCORE;
|
|
|
|
if ( is_hardcore_mode() )
|
|
level.alien_cycle_table = level.alien_cycle_table_hardcore;
|
|
|
|
// Base count multiplier
|
|
|
|
if ( !isdefined( level.base_player_count_multiplier ) )
|
|
{
|
|
level.base_player_count_multiplier = BASE_PLAYER_COUNT_MULTIPLIER;
|
|
level.additional_player_count_multiplier = ADDITIONAL_PLAYER_COUNT_MULTIPLIER;
|
|
}
|
|
|
|
|
|
// Difficulty scalars for hardcore
|
|
if ( !IsDefined( level.hardcore_spawn_multiplier ) )
|
|
level.hardcore_spawn_multiplier = 1.0;
|
|
|
|
if ( !IsDefined( level.hardcore_damage_scalar ) )
|
|
level.hardcore_damage_scalar = 1.0;
|
|
|
|
if ( !IsDefined( level.hardcore_health_scalar ) )
|
|
level.hardcore_health_scalar = 1.0;
|
|
|
|
if ( !IsDefined( level.hardcore_reward_scalar ) )
|
|
level.hardcore_reward_scalar = 1.0;
|
|
|
|
if ( !IsDefined( level.hardcore_score_scalar ) )
|
|
level.hardcore_score_scalar = 1.0;
|
|
|
|
//casual settings
|
|
if ( !IsDefined( level.casual_spawn_multiplier ) )
|
|
level.casual_spawn_multiplier = 1.0;
|
|
|
|
if ( !IsDefined( level.casual_damage_scalar ) )
|
|
level.casual_damage_scalar = 0.5;
|
|
|
|
if ( !IsDefined( level.casual_health_scalar ) )
|
|
level.casual_health_scalar = 0.5;
|
|
|
|
if ( !IsDefined( level.casual_reward_scalar ) )
|
|
level.casual_reward_scalar = 1.0;
|
|
|
|
if ( !IsDefined( level.casual_score_scalar ) )
|
|
level.casual_score_scalar = 0.5;
|
|
|
|
|
|
level.cycle_data = SpawnStruct();
|
|
level.lanes = [];
|
|
|
|
load_cycles_from_table( level.cycle_data );
|
|
init_spawn_node_info( level.cycle_data );
|
|
build_spawn_zones( level.cycle_data );
|
|
load_spawn_events_from_table( level.cycle_data );
|
|
load_variables_from_table( level.cycle_data );
|
|
load_cycle_scalars_from_table( level.cycle_data );
|
|
load_cycle_drill_layer_from_table( level.cycle_data );
|
|
load_random_hives_from_table();
|
|
load_encounter_lanes_from_table();
|
|
populate_lane_spawners();
|
|
|
|
level.cycle_data.current_wave_types = create_wave_types_array();
|
|
level.cycle_data.current_spawn_event_wave_types = create_wave_types_array();
|
|
|
|
level thread spawn_type_vo_monitor();
|
|
level thread monitor_meteor_spawn();
|
|
level thread monitor_ground_spawn();
|
|
|
|
level.pending_meteor_spawns = 0;
|
|
level.pending_ground_spawns = 0;
|
|
level.pending_custom_spawns = 0;
|
|
level.cycle_spawning_active = false;
|
|
|
|
if ( isPlayingSolo() )
|
|
level.cycle_data.max_alien_count = int ( ceil( MAX_ALIEN_COUNT * level.base_player_count_multiplier ) );
|
|
else
|
|
level.cycle_data.max_alien_count = MAX_ALIEN_COUNT;
|
|
|
|
/#
|
|
//level print_cycle_rewards();
|
|
level thread monitor_debug_dvar();
|
|
#/
|
|
}
|
|
|
|
load_variables_from_table( cycle_data )
|
|
{
|
|
cycle_data.min_spawn_interval = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_MIN_SPAWN_INTERVAL, TABLE_VARIABLE_COLUMN ) );
|
|
|
|
safeSpawnDistanceValue = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_SAFE_SPAWN_DISTANCE, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.safe_spawn_distance_sq = safeSpawnDistanceValue * safeSpawnDistanceValue;
|
|
|
|
cycle_data.spawn_point_last_used_duration = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_SPAWN_POINT_LAST_USED_DURATION, TABLE_VARIABLE_COLUMN ) ) * 1000.0;
|
|
|
|
breakAwayDistance = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_GROUP_BREAK_AWAY_DISTANCE, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.group_break_away_distance_sq = breakAwayDistance * breakAwayDistance;
|
|
|
|
cycle_data.player_in_zone_score_modifier = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_PLAYER_IN_ZONE_SCORE, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.current_attacker_in_zone_score_modifier = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_CURRENT_ATTACKER_ZONE_SCORE, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.recently_used_spawn_zone_score_modifier = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_RECENTLY_SPAWNED_ZONE_MODIFIER, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.max_break_away_zone_score_increase = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_MAX_BREAK_AWAY_SCORE_INCREASE, TABLE_VARIABLE_COLUMN ) );
|
|
|
|
cycle_data.spawn_event_min_activation_time = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_SPAWN_EVENT_MIN_ACTIVATION_TIME, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.spawn_event_per_alien_activation_increase = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_SPAWN_EVENT_PER_ALIEN_ACTIVATION_INCREASE, TABLE_VARIABLE_COLUMN ) );
|
|
cycle_data.spawn_event_max_activation_increase = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, TABLE_SPAWN_EVENT_MAX_ACTIVATION_TIME, TABLE_VARIABLE_COLUMN ) );
|
|
|
|
}
|
|
|
|
load_encounter_lanes_from_table()
|
|
{
|
|
level.encounter_lanes = [];
|
|
|
|
for ( entryIndex = TABLE_LANES_START_INDEX; entryIndex <= TABLE_LANES_END_INDEX; entryIndex++ )
|
|
{
|
|
encounter = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_LANES_ENCOUNTER );
|
|
if ( encounter == "" )
|
|
break;
|
|
|
|
if ( !IsDefined( level.encounter_lanes[encounter] ) )
|
|
level.encounter_lanes[encounter] = [];
|
|
|
|
activationTimes = strtok( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_LANES_ACTIVATION_TIME ), ", " );
|
|
names = strTok( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_LANES_NAMES ), " ,()" );
|
|
lanes = get_lanes_from_names( names );
|
|
isRandomSequence = ( ToLower( names[0] ) == "random_sequence" && names.size > 1 );
|
|
|
|
currentIndex = 0;
|
|
foreach ( time in activationTimes )
|
|
{
|
|
newLanes = [];
|
|
if ( isRandomSequence )
|
|
{
|
|
newLanes[0] = lanes[currentIndex];
|
|
currentIndex++;
|
|
if ( currentIndex >= lanes.size )
|
|
currentIndex = 0;
|
|
}
|
|
else
|
|
{
|
|
newLanes = lanes;
|
|
}
|
|
|
|
add_new_encounter_lane( encounter, newLanes, time );
|
|
}
|
|
|
|
foreach ( laneName in lanes )
|
|
{
|
|
if ( IsDefined( level.lanes[laneName] ) )
|
|
continue;
|
|
|
|
level.lanes[laneName] = [];
|
|
}
|
|
}
|
|
|
|
foreach ( index, encounterLane in level.encounter_lanes )
|
|
level.encounter_lanes[index] = sort_array( level.encounter_lanes[index], ::sort_encounter_level_activation_time_func );
|
|
}
|
|
|
|
add_new_encounter_lane( encounter, lanes, time )
|
|
{
|
|
encounterLane = SpawnStruct();
|
|
encounterLane.lanes = lanes;
|
|
encounterLane.activation_time = int( time ) * 1000.0; //ms
|
|
|
|
laneIndex = level.encounter_lanes[encounter].size;
|
|
level.encounter_lanes[encounter][laneIndex] = encounterLane;
|
|
}
|
|
|
|
get_lanes_from_names( lane_names )
|
|
{
|
|
isRandomSequence = ( ToLower( lane_names[0] ) == "random_sequence" && lane_names.size > 1 );
|
|
isRandom = ( ToLower( lane_names[0] ) == "random" && lane_names.size > 1 );
|
|
lanes = [];
|
|
|
|
if ( isRandomSequence )
|
|
{
|
|
lanes = array_remove_index( lane_names, 0 );
|
|
lanes = array_randomize( lanes );
|
|
}
|
|
else if ( isRandom )
|
|
{
|
|
randomIndex = RandomIntRange( 1, lane_names.size );
|
|
lanes[0] = lane_names[randomIndex];
|
|
}
|
|
else
|
|
{
|
|
lanes = lane_names;
|
|
}
|
|
|
|
return lanes;
|
|
}
|
|
|
|
populate_lane_spawners()
|
|
{
|
|
foreach ( index, lane in level.lanes )
|
|
{
|
|
laneStruct = getstruct( index, "targetname" );
|
|
|
|
/# AssertEx( IsDefined( laneStruct), "Can't find struct in level for lane: " + index ); #/
|
|
/# AssertEx( IsDefined( laneStruct.script_linkTo ), "Missing script_linkTo KVP for lane: " + index ); #/
|
|
|
|
linkedSpawners = laneStruct get_links();
|
|
|
|
/# AssertEx( linkedSpawners.size > 0, "No attached spawners for lane: " + index ); #/
|
|
|
|
foreach ( spawnerName in linkedSpawners )
|
|
{
|
|
spawners = find_spawners_by_script_name( spawnerName );
|
|
level.lanes[index] = array_combine( level.lanes[index], spawners );
|
|
}
|
|
}
|
|
}
|
|
|
|
find_spawners_by_script_name( script_name )
|
|
{
|
|
spawners = [];
|
|
|
|
for ( spawnIndex = 0; spawnIndex < level.cycle_data.spawner_list.size; spawnIndex++ )
|
|
{
|
|
if ( !IsDefined( level.cycle_data.spawner_list[spawnIndex]["location"].script_linkname ) )
|
|
continue;
|
|
|
|
if ( level.cycle_data.spawner_list[spawnIndex]["location"].script_linkname == script_name )
|
|
spawners[spawners.size] = spawnIndex;
|
|
}
|
|
|
|
return spawners;
|
|
}
|
|
|
|
load_spawn_events_from_table( cycle_data )
|
|
{
|
|
|
|
cycle_data.generic_spawn_events = [];
|
|
|
|
for ( entryIndex = TABLE_SPAWN_EVENT_START_INDEX; entryIndex <= TABLE_SPAWN_EVENT_END_INDEX; entryIndex++ )
|
|
{
|
|
cycle = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_CYCLE );
|
|
if ( cycle == "" )
|
|
break;
|
|
|
|
cycleNum = int( cycle ) - 1;
|
|
/# AssertEx( cycleNum >= -1 && cycleNum < level.cycle_data.spawn_cycles.size, "Spawn event cycle " + cycle + " references non-existent cycle!" );#/
|
|
|
|
spawnEvent = SpawnStruct();
|
|
spawnEvent.activation_notify = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_NOTIFY );
|
|
spawnEvent.id = strtok( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_ID ), " ," );
|
|
spawnEvent.time_limit = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_TIME_LIMIT ) );
|
|
lane_names = strTok( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_LANES ), " ," );
|
|
if ( lane_names.size > 0 )
|
|
{
|
|
lanes = get_lanes_from_names( lane_names );
|
|
spawnEvent.lanes = lanes;
|
|
foreach ( laneName in lanes )
|
|
{
|
|
if ( IsDefined( level.lanes[laneName] ) )
|
|
continue;
|
|
|
|
level.lanes[laneName] = [];
|
|
}
|
|
}
|
|
|
|
if ( cycleNum == -1 )
|
|
{
|
|
spawnEvent.allow_initial_delay = false;
|
|
newIndex = cycle_data.generic_spawn_events.size;
|
|
cycle_data.generic_spawn_events[newIndex] = spawnEvent;
|
|
}
|
|
else
|
|
{
|
|
spawnEvent.allow_initial_delay = true;
|
|
newIndex = cycle_data.spawn_cycles[cycleNum].spawn_events.size;
|
|
cycle_data.spawn_cycles[cycleNum].spawn_events[newIndex] = spawnEvent;
|
|
}
|
|
}
|
|
|
|
load_spawn_event_waves_from_table();
|
|
}
|
|
|
|
load_spawn_event_waves_from_table()
|
|
{
|
|
level.spawn_event_waves = [];
|
|
|
|
for ( entryIndex = TABLE_SPAWN_EVENT_WAVE_START_INDEX; entryIndex <= TABLE_SPAWN_EVENT_WAVE_END_INDEX; entryIndex++ )
|
|
{
|
|
spawnEventID = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_WAVE_ID );
|
|
if ( spawnEventID == "" )
|
|
break;
|
|
|
|
if ( !IsDefined ( level.spawn_event_waves[spawnEventID] ) )
|
|
level.spawn_event_waves[spawnEventID] = [];
|
|
|
|
spawnEventWave = SpawnStruct();
|
|
spawnEventWave.blocking = get_spawn_event_wave_blocking_by_index( entryIndex );
|
|
spawnEventWave.spawn_delay = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_EVENT_ID ) );
|
|
spawnEventWave.entry_index = entryIndex;
|
|
|
|
addIndex = level.spawn_event_waves[spawnEventID].size;
|
|
level.spawn_event_waves[spawnEventID][addIndex] = spawnEventWave;
|
|
}
|
|
}
|
|
|
|
get_spawn_event_wave_blocking_by_index( index )
|
|
{
|
|
blocking = tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_SPAWN_EVENT_WAVE_BLOCKING );
|
|
|
|
return blocking == "yes";
|
|
}
|
|
|
|
clear_spawn_event_wave_types()
|
|
{
|
|
foreach ( waveType in level.cycle_data.current_spawn_event_wave_types )
|
|
{
|
|
waveType.type_name = undefined;
|
|
waveType.min_spawned = undefined;
|
|
waveType.max_spawned = undefined;
|
|
waveType.max_of_type = undefined;
|
|
}
|
|
}
|
|
|
|
get_spawn_event_types_array( cycle )
|
|
{
|
|
clear_spawn_event_wave_types();
|
|
|
|
get_type_data( get_available_type_data( level.cycle_data.current_spawn_event_wave_types ), cycle, TABLE_SPAWN_EVENT_WAVE_TYPE1, TABLE_SPAWN_EVENT_WAVE_COUNT1 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_spawn_event_wave_types ), cycle, TABLE_SPAWN_EVENT_WAVE_TYPE2, TABLE_SPAWN_EVENT_WAVE_COUNT2 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_spawn_event_wave_types ), cycle, TABLE_SPAWN_EVENT_WAVE_TYPE3, TABLE_SPAWN_EVENT_WAVE_COUNT3 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_spawn_event_wave_types ), cycle, TABLE_SPAWN_EVENT_WAVE_TYPE4, TABLE_SPAWN_EVENT_WAVE_COUNT4 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_spawn_event_wave_types ), cycle, TABLE_SPAWN_EVENT_WAVE_TYPE5, TABLE_SPAWN_EVENT_WAVE_COUNT5 );
|
|
|
|
level.cycle_data.current_spawn_event_wave_types = sort_array( level.cycle_data.current_spawn_event_wave_types, ::sort_priority_levels_func );
|
|
|
|
return level.cycle_data.current_spawn_event_wave_types;
|
|
}
|
|
|
|
/#
|
|
monitor_debug_dvar()
|
|
{
|
|
SetDevDvarIfUninitialized( "debug_spawn_director", 0 );
|
|
SetDevDvarIfUninitialized( "spawn_director_timed_wave_test", 1 );
|
|
SetDevDvarIfUninitialized( "scr_alienwavedebug", "-1 0.0" );
|
|
SetDevDvarIfUninitialized( "scr_alienspawninfo", 0 );
|
|
SetDevDvarIfUninitialized( "scr_alienspawneventinfo", 0 );
|
|
|
|
level.debug_spawn_director_active = false;
|
|
level.debug_spawn_director_spawn_index = 0;
|
|
level.debug_spawn_director_spawn_list = [];
|
|
|
|
level.spawn_director_debug_queue = [];
|
|
level thread debug_queue_monitor();
|
|
|
|
while( true )
|
|
{
|
|
waveDebugValues = strtok( GetDvar( "scr_alienwavedebug", "-1 0.0" ), " " );
|
|
AssertEx( waveDebugValues.size == 2, "Invalid entry for scr_alienwavedebug dvar! Should be integer value for cycle number, and float value (0.0 - 1.0) for intensity level. ( scr_alienwavedebug \"4 0.5\" )" );
|
|
waveDebugCycle = int( waveDebugValues[0] ) - 1;
|
|
if ( waveDebugCycle >= 0 )
|
|
{
|
|
AssertEx( waveDebugCycle < level.cycle_data.spawn_cycles.size, "Invalid cycle entry! Should be between 1 and " + level.cycle_data.spawn_cycles.size );
|
|
if ( alien_mode_has( "nogame" ) )
|
|
{
|
|
level.current_cycle = level.cycle_data.spawn_cycles[ waveDebugCycle ];
|
|
level.current_intensity = float( waveDebugValues[1] ) / ( level.current_cycle.fullIntensityTime * 0.001 );
|
|
level.current_intensity_level = calculate_current_intensity_level();
|
|
level.pending_meteor_spawns = 0;
|
|
level.pending_ground_spawns = 0;
|
|
level.pending_custom_spawns = 0;
|
|
level thread monitor_meteor_spawn();
|
|
level thread monitor_ground_spawn();
|
|
types = get_current_wave_ai_types();
|
|
spawn_wave( types, "debug spawn" );
|
|
}
|
|
else
|
|
{
|
|
AssertMsg( "Can't activate scr_alienwavedebug unless nogame mode is active!" );
|
|
}
|
|
SetDevDvar( "scr_alienwavedebug", "-1 0.0" );
|
|
}
|
|
|
|
if ( !level.debug_spawn_director_active && GetDvarInt( "debug_spawn_director", 0 ) > 0 )
|
|
{
|
|
level.debug_spawn_director_active = true;
|
|
level notify( "debug_mode_activated" );
|
|
level thread debug_respawn_monitor();
|
|
|
|
if ( IsDefined( level.current_cycle ) )
|
|
{
|
|
agents = maps\mp\agents\_agent_utility::getActiveAgentsOfType( "alien" );
|
|
foreach ( ai in agents )
|
|
{
|
|
ai Suicide();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( level.debug_spawn_director_active && GetDvarInt( "debug_spawn_director", 0 ) <= 0 )
|
|
{
|
|
level notify( "debug_mode_deactivated" );
|
|
level.debug_spawn_director_active = false;
|
|
level.debug_spawn_director_spawn_index = 0;
|
|
|
|
foreach ( killAI in level.debug_spawn_director_spawn_list )
|
|
{
|
|
killAI Suicide();
|
|
}
|
|
}
|
|
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
print_cycle_rewards()
|
|
{
|
|
println( "Cycle Rewards: " );
|
|
|
|
foreach ( index, cycle in level.cycle_data.spawn_cycles )
|
|
{
|
|
total_min = 0;
|
|
total_max = 0;
|
|
foreach ( intensitylevel in cycle.intensitylevels )
|
|
{
|
|
aiTypes = get_types_array( intensitylevel.tableIndex );
|
|
foreach ( ai_type in aiTypes )
|
|
{
|
|
amount = level.alien_types[ ai_type.type_name ].attributes[ "reward" ] * 2.0;
|
|
total_min += amount * ai_type.min_spawned;
|
|
|
|
if ( IsDefined( ai_type.max_spawned ) )
|
|
total_max += amount * ai_type.max_spawned;
|
|
}
|
|
}
|
|
|
|
foreach ( spawn_event in cycle.spawn_events )
|
|
{
|
|
foreach ( wave in spawn_event.waves )
|
|
{
|
|
foreach ( ai_type in wave.types )
|
|
{
|
|
amount = level.alien_types[ ai_type.type_name ].attributes[ "reward" ] * 2.0;
|
|
total_min += amount * ai_type.min_spawned;
|
|
|
|
if ( IsDefined( ai_type.max_spawned ) )
|
|
total_max += amount * ai_type.max_spawned;
|
|
}
|
|
}
|
|
}
|
|
println( " Cycle " + index + ": " + total_min + " (Min), " + total_max + " (Max)" );
|
|
|
|
}
|
|
}
|
|
|
|
#/
|
|
|
|
set_intensity( intensity )
|
|
{
|
|
level.current_intensity = clamp( intensity, 0.0, 1.0 );
|
|
}
|
|
|
|
// TODO: Maybe let it set the intensity on force off as well to get a new default without having to call set_intensity
|
|
force_intensity( force_on, intensity )
|
|
{
|
|
if ( force_on )
|
|
level.forced_current_intensity = clamp( intensity, 0.0, 1.0 );
|
|
else if ( IsDefined( level.forced_current_intensity ) )
|
|
level.forced_current_intensity = undefined;
|
|
}
|
|
|
|
load_cycles_from_table( cycle_data )
|
|
{
|
|
cycle_data.spawn_cycles = [];
|
|
|
|
start_index = 0;
|
|
max_index = TABLE_CYCLE_DEF_INDEX;
|
|
|
|
// We need tons of space for the cycles in Last, so move to 1000-2000
|
|
if( level.script == "mp_alien_last" && !is_chaos_mode())
|
|
{
|
|
start_index = TABLE_1000_CYCLE_START_IDX; // 1000
|
|
max_index = TABLE_1000_CYCLE_END_IDX; // 1999
|
|
}
|
|
|
|
for ( entryIndex = start_index; entryIndex <= max_index; entryIndex++ )
|
|
{
|
|
cycleName = get_cycle_by_index( entryIndex );
|
|
if ( cycleName == "" )
|
|
break;
|
|
|
|
cycleIndex = int( cycleName ) - 1;
|
|
if ( !IsDefined ( cycle_data.spawn_cycles[ cycleIndex ] ) )
|
|
{
|
|
cycle = SpawnStruct();
|
|
cycle.intensityLevels = [];
|
|
cycle.spawn_events = [];
|
|
cycle.lanes = [];
|
|
cycle.type_max_counts = [];
|
|
cycle_data.spawn_cycles[ cycleIndex ] = cycle;
|
|
}
|
|
else
|
|
{
|
|
cycle = cycle_data.spawn_cycles[ cycleIndex ];
|
|
}
|
|
|
|
newLevel = SpawnStruct();
|
|
newLevel.intensityThreshold = get_intensity_threshold_by_index( entryIndex );
|
|
newLevel.tableIndex = entryIndex;
|
|
|
|
cycle.intensityLevels[ cycle.intensityLevels.size ] = newLevel;
|
|
}
|
|
|
|
foreach ( cycle in cycle_data.spawn_cycles )
|
|
{
|
|
cycle.intensityLevels = sort_array( cycle.intensityLevels, ::sort_intensity_levels_func );
|
|
|
|
// TODO: Temp change so that string table can be laid out in seconds instead of intensity threshold
|
|
if ( cycle.intensityLevels.size > 0 )
|
|
{
|
|
fullIntensityTime = cycle.intensityLevels[cycle.intensityLevels.size - 1].intensityThreshold;
|
|
foreach( intensityLevel in cycle.intensityLevels )
|
|
{
|
|
if ( fullIntensityTime <= 0.0 )
|
|
{
|
|
intensityLevel.intensityThreshold = 0.0;
|
|
}
|
|
else
|
|
{
|
|
intensityLevel.intensityThreshold = intensityLevel.intensityThreshold / fullIntensityTime;
|
|
}
|
|
}
|
|
cycle.fullIntensityTime = fullIntensityTime * 1000.0; // ms
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
sort_array( array, sort_func, beginning_index, pass_through_parameter_list )
|
|
{
|
|
if ( !IsDefined( beginning_index ) )
|
|
beginning_index = 0;
|
|
|
|
for ( i = beginning_index + 1; i < array.size; i++ )
|
|
{
|
|
entry = array[ i ];
|
|
|
|
for ( j = i - 1; j >= beginning_index && [[ sort_func ]]( array[j], entry, pass_through_parameter_list ); j-- )
|
|
array[ j + 1 ] = array[ j ];
|
|
|
|
array[ j + 1 ] = entry;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
sort_intensity_levels_func( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
/#Assert( IsDefined( test_entry.intensityThreshold ) && IsDefined( base_entry.intensityThreshold ) );#/
|
|
return test_entry.intensityThreshold > base_entry.intensityThreshold;
|
|
}
|
|
|
|
sort_encounter_level_activation_time_func( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
/#Assert( IsDefined( test_entry.activation_time ) && IsDefined( base_entry.activation_time ) );#/
|
|
|
|
return test_entry.activation_time > base_entry.activation_time;
|
|
}
|
|
|
|
sort_priority_levels_func( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
if ( !IsDefined( base_entry.type_name ) )
|
|
return false;
|
|
|
|
if ( !IsDefined( test_entry.type_name ) )
|
|
return true;
|
|
|
|
testTypeName = get_translated_ai_type( test_entry.type_name );
|
|
baseTypeName = get_translated_ai_type( base_entry.type_name );
|
|
|
|
testPriorityLevel = level.alien_types[ testTypeName ].attributes[ "attacker_priority" ];
|
|
basePriorityLevel = level.alien_types[ baseTypeName ].attributes[ "attacker_priority" ];
|
|
return testPriorityLevel > basePriorityLevel;
|
|
}
|
|
|
|
sort_closest_distance_to_players_func ( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
testDistance = get_average_distance_to_players( test_entry["location"].origin );
|
|
baseDistance = get_average_distance_to_players( base_entry["location"].origin );
|
|
|
|
return testDistance > baseDistance;
|
|
}
|
|
|
|
sort_player_view_direction_func ( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
player_pos = pass_through_parameter_list[0];
|
|
player_view_dir = pass_through_parameter_list[1];
|
|
|
|
player_to_base_dot = get_player_to_node_dot( player_pos, player_view_dir, base_entry["location"] );
|
|
player_to_test_dot = get_player_to_node_dot( player_pos, player_view_dir, test_entry["location"] );
|
|
|
|
return player_to_test_dot < player_to_base_dot;
|
|
}
|
|
|
|
get_player_to_node_dot( player_pos, player_view_dir, node )
|
|
{
|
|
player_to_node = vectorNormalize( node.origin - player_pos );
|
|
return vectorDot( player_to_node, player_view_dir );
|
|
}
|
|
|
|
get_average_distance_to_players( location )
|
|
{
|
|
playerCount = level.players.size;
|
|
|
|
if ( playerCount == 0 )
|
|
return 0;
|
|
|
|
totalDistanceSq = 0;
|
|
foreach ( player in level.players )
|
|
{
|
|
totalDistanceSq += DistanceSquared( player.origin, location );
|
|
}
|
|
|
|
return totalDistanceSq / playerCount;
|
|
}
|
|
|
|
/#
|
|
debug_print( debug_string )
|
|
{
|
|
level.spawn_director_debug_queue[level.spawn_director_debug_queue.size] = debug_string;
|
|
}
|
|
|
|
debug_queue_monitor()
|
|
{
|
|
DEBUG_INTERVAL = 2.0;
|
|
nextValidPrintTime = GetTime() - DEBUG_INTERVAL;
|
|
|
|
while( true )
|
|
{
|
|
currentTime = GetTime();
|
|
|
|
if ( level.spawn_director_debug_queue.size > 0 )
|
|
{
|
|
IPrintLnBold( level.spawn_director_debug_queue[0] );
|
|
level.spawn_director_debug_queue = array_remove( level.spawn_director_debug_queue, level.spawn_director_debug_queue[0] );
|
|
wait DEBUG_INTERVAL;
|
|
}
|
|
else
|
|
{
|
|
wait 0.2;
|
|
}
|
|
}
|
|
}
|
|
#/
|
|
|
|
build_spawn_zones( cycle_data )
|
|
{
|
|
spawnLocations = getstructarray( "alien_spawn_struct", "targetname" ); // temp to handle areas lacking in spawn zones for now
|
|
spawnZones = GetEntArray( "spawn_zone", "targetname" );
|
|
|
|
cycle_data.spawn_zones = [];
|
|
|
|
foreach ( zone in spawnZones )
|
|
{
|
|
/# AssertEx( !spawn_zone_exists( zone.script_linkName, cycle_data ), "Spawn zone with script_linkName of " + zone.script_linkName + " defined multiple times!" );#/
|
|
|
|
zoneInfo = SpawnStruct();
|
|
zoneInfo.zone_name = zone.script_linkName;
|
|
zoneInfo.volume = zone;
|
|
zoneInfo.spawn_nodes = [];
|
|
cycle_data.spawn_zones[ zone.script_linkName ] = zoneInfo;
|
|
}
|
|
|
|
cycle_data.spawner_list = [];
|
|
|
|
put_spawnLocations_into_cycle_data( spawnLocations, cycle_data );
|
|
|
|
/#
|
|
foreach ( zone in cycle_data.spawn_zones )
|
|
{
|
|
AssertEx( zone.spawn_nodes.size > 0, "Spawn zone " + zone.zone_name + " does not have any linked spawners!" );
|
|
}
|
|
#/
|
|
}
|
|
|
|
put_spawnLocations_into_cycle_data( spawnLocations, cycle_data )
|
|
{
|
|
foreach ( location in spawnLocations )
|
|
{
|
|
validTypes = [];
|
|
|
|
if ( isDefined( level.adjust_spawnLocation_func ) )
|
|
location = [[level.adjust_spawnLocation_func]]( location );
|
|
|
|
if ( IsDefined( location.script_noteworthy ) )
|
|
validTypes = strtok( cycle_data.spawn_node_info [ location.script_noteworthy ].validType, " " );
|
|
|
|
locationInfo = [];
|
|
locationInfo["types"] = validTypes;
|
|
locationInfo["location"] = location;
|
|
|
|
cycle_data.spawner_list[cycle_data.spawner_list.size] = locationInfo;
|
|
if ( !IsDefined( location.script_linkTo ) )
|
|
continue;
|
|
|
|
linkedZones = location get_links();
|
|
foreach ( zone in linkedZones )
|
|
{
|
|
/#assertex( IsDefined( cycle_data.spawn_zones[zone] ), "Invalid linked spawn zone: " + zone );#/
|
|
locationIndex = cycle_data.spawn_zones[zone].spawn_nodes.size;
|
|
cycle_data.spawn_zones[zone].spawn_nodes[locationIndex] = locationInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
spawn_zone_exists( zone_name, cycle_data )
|
|
{
|
|
foreach( index, zone in cycle_data.spawn_zones )
|
|
{
|
|
if ( index == zone_name )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
can_spawn_type( ai_type )
|
|
{
|
|
if ( !IsDefined( level.current_cycle ) || !IsDefined( level.current_cycle.type_max_counts[ai_type] ) )
|
|
return ( ( get_max_alien_count() - get_current_agent_count() ) > 0 );
|
|
|
|
return ( level.current_cycle.type_max_counts[ai_type] - get_current_agent_count_of_type( ai_type ) > 0 );
|
|
}
|
|
|
|
reserve_custom_spawn_space( space_size, allow_partial_space, reserve_type )
|
|
{
|
|
if ( !IsDefined( allow_partial_space ) )
|
|
allow_partial_space = false;
|
|
|
|
currentCount = get_current_agent_count();
|
|
|
|
if ( allow_partial_space )
|
|
{
|
|
if ( IsDefined( reserve_type ) && IsDefined( level.current_cycle ) && IsDefined( level.current_cycle.type_max_counts[reserve_type] ) )
|
|
space_size = Clamp( level.current_cycle.type_max_counts[reserve_type] - get_current_agent_count_of_type( reserve_type ), 0, space_size );
|
|
|
|
availableSpace = clamp( get_max_alien_count() - currentCount, 0, space_size );
|
|
}
|
|
else if ( currentCount + space_size <= get_max_alien_count() )
|
|
{
|
|
availableSpace = space_size;
|
|
}
|
|
else
|
|
{
|
|
availableSpace = 0;
|
|
}
|
|
|
|
level.pending_custom_spawns += availableSpace;
|
|
|
|
return availableSpace;
|
|
}
|
|
|
|
process_custom_spawn( alien_type, spawn_point, intro_anim )
|
|
{
|
|
currentSpawned = get_current_agent_count( true, false, true );
|
|
if ( currentSpawned >= get_max_alien_count() )
|
|
{
|
|
AssertMsg( "Unable to process custom spawn. Already at max spawn count! Make sure to reserve needed space." );
|
|
alien = undefined;
|
|
}
|
|
else
|
|
{
|
|
alien = process_spawn( alien_type, spawn_point, intro_anim );
|
|
}
|
|
|
|
level.pending_custom_spawns = Max( 0, level.pending_custom_spawns - 1 );
|
|
|
|
return alien;
|
|
}
|
|
|
|
release_custom_spawn_space( space_size )
|
|
{
|
|
level.pending_custom_spawns = Max( 0, level.pending_custom_spawns - space_size );
|
|
}
|
|
|
|
get_cycle_by_index( index )
|
|
{
|
|
return tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_CYCLE );
|
|
}
|
|
|
|
get_intensity_level_by_index( index )
|
|
{
|
|
return tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_INTENSITY );
|
|
}
|
|
|
|
get_respawn_threshold_by_index( index )
|
|
{
|
|
respawnThreshold = tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_RESPAWN_THRESHOLD );
|
|
|
|
if ( respawnThreshold == "" || respawnThreshold == " " )
|
|
return undefined;
|
|
|
|
return int( respawnThreshold );
|
|
}
|
|
|
|
get_respawn_delay_by_index( index )
|
|
{
|
|
respawnDelay = tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_RESPAWN_DELAY );
|
|
|
|
if ( respawnDelay == "" || respawnDelay == " " )
|
|
return undefined;
|
|
|
|
return float( respawnDelay );
|
|
}
|
|
|
|
get_intensity_threshold_by_index( index )
|
|
{
|
|
return float( tablelookup( level.alien_cycle_table, TABLE_INDEX, index, TABLE_INTENSITY_THRESHOLD ) );
|
|
}
|
|
|
|
get_types_array( cycle )
|
|
{
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES1, TABLE_COUNT1 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES2, TABLE_COUNT2 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES3, TABLE_COUNT3 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES4, TABLE_COUNT4 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES5, TABLE_COUNT5 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES6, TABLE_COUNT6 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES7, TABLE_COUNT7 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES8, TABLE_COUNT8 );
|
|
get_type_data( get_available_type_data( level.cycle_data.current_wave_types ), cycle, TABLE_TYPES9, TABLE_COUNT9 );
|
|
|
|
level.cycle_data.current_wave_types = sort_array( level.cycle_data.current_wave_types, ::sort_priority_levels_func );
|
|
}
|
|
|
|
get_available_type_data( type_data_array )
|
|
{
|
|
for ( typeIndex = 0; typeIndex < type_data_array.size; typeIndex++ )
|
|
{
|
|
if ( !IsDefined( type_data_array[typeIndex].type_name ) )
|
|
return type_data_array[typeIndex];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
get_type_data( type_data, row_index, table_ai_type, table_count_type )
|
|
{
|
|
if ( !IsDefined( type_data ) )
|
|
return undefined;
|
|
|
|
typeName = tablelookup( level.alien_cycle_table, TABLE_INDEX, row_index, table_ai_type );
|
|
|
|
if ( typeName == "" )
|
|
return undefined;
|
|
|
|
typeRange = strTok( tablelookup( level.alien_cycle_table, TABLE_INDEX, row_index, table_count_type ), " " );
|
|
assertex( typeRange.size > 0 && typeRange.size <= 3, typeName + " doesn't have valid spawn range in index " + ( row_index + 1 ) );
|
|
|
|
type_data.type_name = typeName;
|
|
typeRange[0] = int( typeRange[0] );
|
|
typeRange[1] = int( typeRange[1] );
|
|
|
|
if ( typeRange.size == 0 )
|
|
{
|
|
type_data.min_spawned = 0;
|
|
}
|
|
else if ( typeRange.size == 1 )
|
|
{
|
|
type_data.min_spawned = typeRange[0];
|
|
}
|
|
else
|
|
{
|
|
if ( typeRange[0] < typeRange[1] )
|
|
{
|
|
type_data.min_spawned = typeRange[0];
|
|
type_data.max_spawned = typeRange[1];
|
|
}
|
|
else if ( typeRange[1] > typeRange[0] )
|
|
{
|
|
type_data.min_spawned = typeRange[1];
|
|
type_data.max_spawned = typeRange[0];
|
|
}
|
|
else
|
|
{
|
|
type_data.min_spawned = typeRange[0];
|
|
}
|
|
|
|
if ( typeRange.size == 3 )
|
|
type_data.max_of_type = int( typeRange[2] );
|
|
}
|
|
|
|
return type_data;
|
|
}
|
|
|
|
remove_spawn_location( spawner )
|
|
{
|
|
foreach ( spawnZone in level.cycle_data.spawn_zones )
|
|
{
|
|
for ( nodeIndex = 0; nodeIndex < spawnZone.spawn_nodes.size; nodeIndex++ )
|
|
{
|
|
location = spawnZone.spawn_nodes[nodeIndex]["location"];
|
|
if ( !IsDefined( location.script_linkName ) )
|
|
continue;
|
|
|
|
if ( location.script_linkName == spawner )
|
|
{
|
|
spawnZone.spawn_nodes = array_remove_index( spawnZone.spawn_nodes, nodeIndex );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
start_cycle( cycle_num)
|
|
{
|
|
AssertEx( cycle_num > 0 && cycle_num <= level.cycle_data.spawn_cycles.size, cycle_num + " is invalid cycle number" );
|
|
set_cycle_scalars( cycle_num );
|
|
|
|
level.cycle_spawning_active = true;
|
|
level thread spawn_director_loop( cycle_num - 1 );
|
|
level notify( "alien_cycle_started" );
|
|
}
|
|
|
|
end_cycle()
|
|
{
|
|
level notify( "end_cycle" );
|
|
//<NOTE J.C.> This is for external scripts (such as mist and lurker). Maybe we can combine the two notifies soon?
|
|
level.cycle_spawning_active = false;
|
|
level notify( "alien_cycle_ended" );
|
|
}
|
|
|
|
pause_cycle( time_to_pause )
|
|
{
|
|
if ( !level.cycle_spawning_active )
|
|
return;
|
|
|
|
level thread pause_cycle_internal( time_to_pause );
|
|
}
|
|
|
|
pause_cycle_internal( time_to_pause )
|
|
{
|
|
level.intensity_spawning_paused_count++;
|
|
wait time_to_pause;
|
|
level.intensity_spawning_paused_count = int( Max( 0, level.intensity_spawning_paused_count - 1 ) );
|
|
}
|
|
|
|
activate_spawn_event( event_notify, wait_for_completion )
|
|
{
|
|
if ( !IsDefined( wait_for_completion ) )
|
|
wait_for_completion = false;
|
|
|
|
spawnEvent = find_spawn_event( event_notify );
|
|
|
|
if ( !IsDefined( spawnEvent ) )
|
|
{
|
|
if ( IsDefined( level.current_cycle_num ) )
|
|
outputCycleNum = level.current_cycle_num + 1;
|
|
else
|
|
outputCycleNum = 0;
|
|
|
|
AssertMsg( event_notify + " is not a valid notify for cycle " + outputCycleNum + " and is not in the generic spawn events" );
|
|
return;
|
|
}
|
|
|
|
if ( wait_for_completion )
|
|
level run_spawn_event( spawnEvent, event_notify );
|
|
else
|
|
level thread run_spawn_event( spawnEvent, event_notify );
|
|
}
|
|
|
|
find_spawn_event( event_notify )
|
|
{
|
|
if ( IsDefined( level.current_cycle ) )
|
|
{
|
|
foreach ( spawnEvent in level.current_cycle.spawn_events )
|
|
{
|
|
if ( spawnEvent.activation_notify == event_notify )
|
|
return spawnEvent;
|
|
}
|
|
}
|
|
|
|
foreach ( spawnEvent in level.cycle_data.generic_spawn_events )
|
|
{
|
|
if ( spawnEvent.activation_notify == event_notify )
|
|
return spawnEvent;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
wait_for_spawn_event_delay()
|
|
{
|
|
while ( level.pending_meteor_spawns > 0 || level.pending_ground_spawns > 0 )
|
|
wait 0.05;
|
|
|
|
timeToWait = level.cycle_data.spawn_event_min_activation_time + level.cycle_data.spawn_event_per_alien_activation_increase * get_current_agent_count();
|
|
timeToWait = Min( level.cycle_data.spawn_event_max_activation_increase, timeToWait );
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Initial Event Delay: " + timeToWait + ", Aliens alive: " + get_current_agent_count() );
|
|
}
|
|
#/
|
|
|
|
wait timeToWait;
|
|
}
|
|
|
|
run_spawn_event( spawn_event, event_notify )
|
|
{
|
|
level endon( "end_cycle" );
|
|
level endon( "nuke_went_off" );
|
|
level endon( "game_ended" );
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Spawn Event Activated: " + event_notify + ", ID: " + spawn_event.id );
|
|
debug_print( "Time Limit: " + spawn_event.time_limit + ", Number of waves: " + spawn_event.waves.size );
|
|
}
|
|
#/
|
|
|
|
if ( level.cycle_spawning_active )
|
|
level.intensity_spawning_paused_count++;
|
|
|
|
if ( spawn_event.allow_initial_delay )
|
|
wait_for_spawn_event_delay();
|
|
|
|
level thread spawn_event_time_limit_monitor( spawn_event.time_limit, spawn_event.activation_notify );
|
|
level thread process_spawn_event_spawning( spawn_event );
|
|
waittill_any( "spawn_event_complete" + spawn_event.activation_notify, "spawn_event_time_limit_reached" + spawn_event.activation_notify );
|
|
|
|
if ( level.cycle_spawning_active )
|
|
{
|
|
level.intensity_spawning_paused_count = int ( Max( 0, level.intensity_spawning_paused_count - 1 ) );
|
|
AssertEx( level.intensity_spawning_paused_count >= 0, "Spawning pause count below zero! Tell a programmer!" );
|
|
}
|
|
}
|
|
|
|
process_spawn_event_spawning( spawn_event )
|
|
{
|
|
allEventSpawnedAliens = [];
|
|
|
|
randomSpawnEvent = RandomInt( spawn_event.id.size );
|
|
eventIndex = spawn_event.id[randomSpawnEvent];
|
|
|
|
level.override_lane_index = 0;
|
|
|
|
foreach( wave in level.spawn_event_waves[eventIndex] )
|
|
{
|
|
if ( wave.spawn_delay > 0.0 )
|
|
{
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Spawn event wave delay for " + wave.spawn_delay + " seconds." );
|
|
}
|
|
#/
|
|
wait wave.spawn_delay;
|
|
}
|
|
|
|
if ( IsDefined( spawn_event.lanes ) )
|
|
lanes = spawn_event.lanes;
|
|
else
|
|
lanes = undefined;
|
|
|
|
wave.types = get_spawn_event_types_array( wave.entry_index );
|
|
|
|
if ( wave_has_delayed_spawn_type( wave ) )
|
|
spawnedAliens = spawn_event_delayed_wave_spawn( wave, spawn_event.activation_notify, lanes );
|
|
else
|
|
spawnedAliens = spawn_event_wave_spawn( wave, spawn_event.activation_notify, lanes );
|
|
|
|
allEventSpawnedAliens = array_combine( allEventSpawnedAliens, spawnedAliens );
|
|
}
|
|
|
|
wait_for_all_aliens_killed( allEventSpawnedAliens, spawn_event.activation_notify );
|
|
level notify( "spawn_event_complete" + spawn_event.activation_notify );
|
|
}
|
|
|
|
wave_has_delayed_spawn_type( wave )
|
|
{
|
|
if ( wave_has_type( wave, "minion" ) )
|
|
return true;
|
|
|
|
if ( wave_has_type( wave, "elite" ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wave_has_type( wave, ai_type )
|
|
{
|
|
foreach( alienType in wave.types )
|
|
{
|
|
if ( !IsDefined( alienType.type_name ) )
|
|
continue;
|
|
|
|
if ( alienType.type_name == ai_type )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
spawn_event_wave_spawn( wave, activation_notify, lanes )
|
|
{
|
|
spawnedAliens = spawn_wave( wave.types, "event", lanes );
|
|
|
|
if ( wave.blocking )
|
|
spawn_event_wave_block( spawnedAliens, activation_notify );
|
|
|
|
return spawnedAliens;
|
|
}
|
|
|
|
spawn_event_wave_block( spawnedAliens, activation_notify )
|
|
{
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Spawn event wave blocking" );
|
|
}
|
|
#/
|
|
|
|
wait_for_all_aliens_killed( spawnedAliens, activation_notify );
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Spawn event wave finished blocking" );
|
|
}
|
|
#/
|
|
}
|
|
|
|
spawn_event_delayed_wave_spawn( wave, activation_notify, lanes )
|
|
{
|
|
spawnedAliens = spawn_wave( wave.types, "event", lanes );
|
|
|
|
if ( level.pending_meteor_spawns > 0 )
|
|
level thread spawn_event_minion_wave_spawn();
|
|
if ( level.pending_ground_spawns > 0 )
|
|
level thread spawn_event_elite_wave_spawn();
|
|
|
|
while ( level.pending_meteor_spawns > 0 || level.pending_ground_spawns > 0 )
|
|
{
|
|
level waittill( "spawn_event_delayed_spawn_complete", aliens );
|
|
spawnedAliens = array_combine( spawnedAliens, aliens );
|
|
wait 0.05; // let the pending values update
|
|
}
|
|
|
|
if ( wave.blocking )
|
|
spawn_event_wave_block( spawnedAliens, activation_notify );
|
|
|
|
return spawnedAliens;
|
|
}
|
|
|
|
spawn_event_minion_wave_spawn()
|
|
{
|
|
spawnedAliens = [];
|
|
|
|
while ( level.pending_meteor_spawns > 0 )
|
|
{
|
|
level waittill( "meteor_aliens_spawned", aliens, requestedCount );
|
|
spawnedAliens = array_combine( spawnedAliens, aliens );
|
|
wait 0.05; // let the pending_meteor_spawns value update
|
|
}
|
|
|
|
level notify( "spawn_event_delayed_spawn_complete", spawnedAliens );
|
|
}
|
|
|
|
spawn_event_elite_wave_spawn()
|
|
{
|
|
spawnedAliens = [];
|
|
|
|
while ( level.pending_ground_spawns > 0 )
|
|
{
|
|
level waittill( "ground_alien_spawned", alien );
|
|
spawnedAliens[spawnedAliens.size] = alien;
|
|
wait 0.05; // let the pending_ground_spawns value update
|
|
}
|
|
|
|
level notify( "spawn_event_delayed_spawn_complete", spawnedAliens );
|
|
}
|
|
|
|
spawn_event_time_limit_monitor( time_limit, activation_notify )
|
|
{
|
|
level endon( "spawn_event_complete" + activation_notify );
|
|
|
|
wait time_limit;
|
|
|
|
/#
|
|
if ( GetDvarInt( "scr_alienspawneventinfo", 0 ) == 1 )
|
|
{
|
|
debug_print( "Spawn Event Timed Out: " + activation_notify + ", Time: " + time_limit );
|
|
}
|
|
#/
|
|
|
|
level notify( "spawn_event_time_limit_reached" + activation_notify );
|
|
}
|
|
|
|
wait_for_all_aliens_killed( aliens, activation_notify )
|
|
{
|
|
level endon( "spawn_event_complete" + activation_notify );
|
|
|
|
while ( true )
|
|
{
|
|
wait 0.05;
|
|
|
|
anyAliveAliens = false;
|
|
foreach ( alien in aliens )
|
|
{
|
|
if ( IsAlive( alien ) )
|
|
{
|
|
anyAliveAliens = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !anyAliveAliens )
|
|
break;
|
|
}
|
|
}
|
|
|
|
create_wave_types_array()
|
|
{
|
|
MAX_NUM_TYPES = 9;
|
|
waveTypes = [];
|
|
|
|
for( typeIndex = 0; typeIndex < MAX_NUM_TYPES; typeIndex++ )
|
|
{
|
|
waveType = SpawnStruct();
|
|
waveType.type_name = undefined;
|
|
waveTypes[ waveTypes.size ] = waveType;
|
|
}
|
|
|
|
return waveTypes;
|
|
}
|
|
|
|
spawn_director_loop( cycle_num )
|
|
{
|
|
level endon( "end_cycle" );
|
|
level endon( "nuke_went_off" );
|
|
level endon( "game_ended" );
|
|
|
|
level.spawn_node_traces_this_frame = 0;
|
|
level.spawn_node_traces_frame_time = GetTime();
|
|
level.intensity_spawning_paused_count = 0;
|
|
|
|
level.current_cycle_num = cycle_num;
|
|
level.current_cycle = level.cycle_data.spawn_cycles[ cycle_num ];
|
|
cycle_begin_intensity_monitor();
|
|
level.pending_meteor_spawns = 0;
|
|
level.pending_ground_spawns = 0;
|
|
|
|
activate_current_lane_monitor();
|
|
|
|
if ( !IsDefined( level.debug_spawn_director_active ) || !level.debug_spawn_director_active )
|
|
initial_spawn();
|
|
|
|
while ( true )
|
|
{
|
|
/#
|
|
while ( level.debug_spawn_director_active )
|
|
wait 0.05;
|
|
#/
|
|
respawnActive = respawn_threshold_monitor();
|
|
|
|
/#
|
|
if ( level.debug_spawn_director_active )
|
|
continue;
|
|
#/
|
|
|
|
respawn( respawnActive );
|
|
}
|
|
}
|
|
|
|
activate_current_lane_monitor()
|
|
{
|
|
if ( is_chaos_mode() )
|
|
return;
|
|
|
|
level thread current_lane_monitor();
|
|
}
|
|
|
|
INTENSITY_MONITOR_FREQUENCY = 0.1;
|
|
|
|
cycle_begin_intensity_monitor()
|
|
{
|
|
level.current_intensity_level = -1;
|
|
level.current_intensity = 0.0;
|
|
|
|
level thread intensity_monitor_update_loop();
|
|
}
|
|
|
|
intensity_monitor_update_loop()
|
|
{
|
|
level endon( "end_cycle" );
|
|
level endon( "nuke_went_off" );
|
|
level endon( "game_ended" );
|
|
|
|
last_intensity_update_time = GetTime();
|
|
|
|
// initial pass: linear increase in intensity over time
|
|
while ( true )
|
|
{
|
|
currentTime = GetTime();
|
|
|
|
if ( level.intensity_spawning_paused_count == 0 )
|
|
{
|
|
if ( IsDefined( level.forced_current_intensity ) )
|
|
{
|
|
level.current_intensity = level.forced_current_intensity;
|
|
}
|
|
else if ( level.current_cycle.fullIntensityTime == 0.0 )
|
|
{
|
|
level.current_intensity = 1.0;
|
|
}
|
|
else
|
|
{
|
|
intensityIncrease = ( currentTime - last_intensity_update_time ) / level.current_cycle.fullIntensityTime;
|
|
level.current_intensity = clamp( level.current_intensity + intensityIncrease, 0.0, 1.0 );
|
|
}
|
|
|
|
last_intensity_update_time = currentTime;
|
|
intensityLevel = calculate_current_intensity_level();
|
|
|
|
if ( level.current_intensity_level != intensityLevel )
|
|
level notify( "intensity_level_changed" );
|
|
|
|
level.current_intensity_level = intensityLevel;
|
|
//debug_print( "Intensity: " + level.current_intensity + ", Intensity Level: " + level.current_intensity_level );
|
|
}
|
|
else
|
|
{
|
|
last_intensity_update_time = currentTime;
|
|
}
|
|
|
|
wait INTENSITY_MONITOR_FREQUENCY;
|
|
}
|
|
}
|
|
|
|
current_lane_monitor()
|
|
{
|
|
level endon( "end_cycle" );
|
|
level endon( "nuke_went_off" );
|
|
level endon( "game_ended" );
|
|
level.cycle_data.current_lane = undefined;
|
|
|
|
currentEncounter = get_current_encounter();
|
|
|
|
while ( !IsDefined( currentEncounter ) )
|
|
{
|
|
wait 0.2;
|
|
currentEncounter = get_current_encounter();
|
|
}
|
|
|
|
if ( !IsDefined( level.encounter_lanes[currentEncounter] ) || level.encounter_lanes[currentEncounter].size == 0 )
|
|
return;
|
|
|
|
while ( true )
|
|
{
|
|
endIndex = undefined;
|
|
foreach ( index, lane in level.encounter_lanes[currentEncounter] )
|
|
{
|
|
laneIntensityThreshold = lane.activation_time / level.current_cycle.fullIntensityTime;
|
|
if ( laneIntensityThreshold > level.current_intensity )
|
|
break;
|
|
|
|
endIndex = index;
|
|
}
|
|
|
|
if ( IsDefined( endIndex ) )
|
|
{
|
|
currentLane = level.encounter_lanes[currentEncounter][endIndex];
|
|
|
|
if ( !IsDefined( level.cycle_data.current_lane ) || currentLane != level.cycle_data.current_lane )
|
|
{
|
|
level.cycle_data.current_lane = currentLane;
|
|
play_lane_change_sound();
|
|
}
|
|
}
|
|
|
|
wait 0.5;
|
|
}
|
|
}
|
|
|
|
play_lane_change_sound()
|
|
{
|
|
laneIndex = get_random_entry( level.cycle_data.current_lane.lanes );
|
|
spawnIndex = level.lanes[laneIndex][ RandomInt( level.lanes[laneIndex].size ) ];
|
|
audioLocation = level.cycle_data.spawner_list[spawnIndex]["location"].origin;
|
|
playSoundAtPos( audioLocation, LANE_CHANGE_SPAWN_SOUND );
|
|
}
|
|
|
|
calculate_current_intensity_level()
|
|
{
|
|
for ( intensityIndex = 0; intensityIndex < level.current_cycle.intensityLevels.size; intensityIndex++ )
|
|
{
|
|
if ( level.current_cycle.intensityLevels[intensityIndex].intensityThreshold > level.current_intensity )
|
|
break;
|
|
}
|
|
|
|
return intensityIndex - 1;
|
|
}
|
|
|
|
initial_spawn()
|
|
{
|
|
while ( level.current_intensity_level < 0 )
|
|
wait 0.05;
|
|
|
|
types = get_current_wave_ai_types();
|
|
spawn_wave( types, "spawn" );
|
|
}
|
|
|
|
get_current_wave_ai_types()
|
|
{
|
|
foreach ( waveType in level.cycle_data.current_wave_types )
|
|
{
|
|
waveType.type_name = undefined;
|
|
waveType.max_of_type = undefined;
|
|
waveType.min_spawned = undefined;
|
|
waveType.max_spawned = undefined;
|
|
}
|
|
|
|
tableIndex = level.current_cycle.intensityLevels[level.current_intensity_level].tableIndex;
|
|
|
|
get_types_array( tableIndex );
|
|
level.cycle_data.current_respawn_threshold = get_respawn_threshold_by_index( tableIndex );
|
|
level.cycle_data.current_respawn_delay = get_respawn_delay_by_index( tableIndex );
|
|
|
|
return level.cycle_data.current_wave_types;
|
|
}
|
|
|
|
respawn( respawnActive )
|
|
{
|
|
if ( IsDefined( respawnActive ) && respawnActive )
|
|
{
|
|
types = level.cycle_data.current_wave_types;
|
|
spawnMethod = "respawn";
|
|
}
|
|
else
|
|
{
|
|
types = get_current_wave_ai_types();
|
|
spawnMethod = "spawn";
|
|
}
|
|
spawn_wave( types, spawnMethod );
|
|
}
|
|
|
|
spawn_wave( types, spawn_method, override_lanes )
|
|
{
|
|
spawnedAliens = [];
|
|
|
|
for ( typesIndex = 0; typesIndex < types.size; typesIndex++ )
|
|
{
|
|
if ( IsDefined( types[typesIndex].type_name ) )
|
|
spawnedAliens = array_combine ( spawn_type( types[ typesIndex ], spawn_method, override_lanes ), spawnedAliens );
|
|
}
|
|
|
|
return spawnedAliens;
|
|
}
|
|
|
|
/#
|
|
debug_respawn_monitor()
|
|
{
|
|
level endon( "debug_mode_deactivated" );
|
|
|
|
level.debug_spawn_director_spawn_list = [];
|
|
|
|
while( true )
|
|
{
|
|
while ( !IsDefined( level.current_intensity_level ) || level.current_intensity_level < 0 )
|
|
wait 0.05;
|
|
|
|
desiredTotalAI = GetDvarInt( "debug_spawn_director", 0 );
|
|
currentlyAlive = [];
|
|
foreach ( testAI in level.debug_spawn_director_spawn_list )
|
|
{
|
|
if ( IsAlive ( testAi ) )
|
|
currentlyAlive[currentlyAlive.size] = testAI;
|
|
}
|
|
level.debug_spawn_director_spawn_list = currentlyAlive;
|
|
|
|
for ( spawnIndex = level.debug_spawn_director_spawn_list.size; spawnIndex < desiredTotalAI; spawnIndex++ )
|
|
{
|
|
alien = spawn_alien( get_random_type_from_current_intensity() );
|
|
|
|
listIndex = level.debug_spawn_director_spawn_list.size;
|
|
level.debug_spawn_director_spawn_list[listIndex] = alien;
|
|
wait level.cycle_data.min_spawn_interval;
|
|
}
|
|
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
get_random_type_from_current_intensity()
|
|
{
|
|
aiTypes = get_current_wave_ai_types();
|
|
randomTypeIndex = RandomInt( aiTypes.size );
|
|
|
|
return aiTypes[randomTypeIndex].type_name;
|
|
}
|
|
|
|
#/
|
|
|
|
respawn_threshold_monitor()
|
|
{
|
|
level endon( "end cycle" );
|
|
level endon( "intensity_level_changed" );
|
|
/#level endon( "debug_mode_activated" );#/
|
|
|
|
while ( true )
|
|
{
|
|
if ( level.current_intensity_level >= 0 && level.intensity_spawning_paused_count == 0 )
|
|
{
|
|
if ( IsDefined( level.cycle_data.current_respawn_threshold ) && level.cycle_data.current_respawn_threshold >= 0)
|
|
{
|
|
currentAITotal = get_current_agent_count();
|
|
respawnThreshold = get_scaled_alien_amount( level.cycle_data.current_respawn_threshold );
|
|
|
|
if ( currentAITotal <= respawnThreshold )
|
|
break;
|
|
}
|
|
}
|
|
|
|
wait 0.1;
|
|
}
|
|
|
|
if ( IsDefined( level.cycle_data.current_respawn_delay ) )
|
|
{
|
|
if ( level.current_intensity_level >= 0 && level.cycle_data.current_respawn_delay >= 0 )
|
|
wait level.cycle_data.current_respawn_delay * get_current_spawn_count_multiplier();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
get_current_spawn_count_multiplier()
|
|
{
|
|
multiplier = level.base_player_count_multiplier + (level.additional_player_count_multiplier * ( level.players.size - 1 ) );
|
|
if ( is_hardcore_mode() )
|
|
{
|
|
multiplier *= level.hardcore_spawn_multiplier;
|
|
}
|
|
|
|
return multiplier;
|
|
}
|
|
|
|
spawn_type_vo_monitor()
|
|
{
|
|
level endon( "game_ended" );
|
|
level endon ( "nuke_went_off" );
|
|
|
|
currentTime = GetTime();
|
|
nextValidSpitterVOTime = currentTime;
|
|
nextValidQueenVOTime = currentTime;
|
|
|
|
spitterVOInterval = 40000; //ms
|
|
queenVOInterval = 30000; //ms
|
|
|
|
while ( true )
|
|
{
|
|
level waittill( "spawned_alien", alien );
|
|
|
|
if ( !isDefined ( alien.alien_type ) )
|
|
continue;
|
|
|
|
if ( flag_exist( "escape_conditions_met" ) && flag ( "escape_conditions_met" ) ) //no more VO once the players have escaped
|
|
return;
|
|
|
|
switch( alien.alien_type )
|
|
{
|
|
case "spitter":
|
|
currentTime = GetTime();
|
|
if ( currentTime >= nextValidSpitterVOTime )
|
|
{
|
|
level thread maps\mp\alien\_music_and_dialog::playVOForSpitterSpawn( alien );
|
|
nextValidSpitterVOTime = currentTime + spitterVOInterval;
|
|
}
|
|
break;
|
|
|
|
case "elite":
|
|
currentTime = GetTime();
|
|
if ( currentTime >= nextValidQueenVOTime )
|
|
{
|
|
level thread maps\mp\alien\_music_and_dialog::playVOForQueenSpawn( alien );
|
|
nextValidQueenVOTime = currentTime + queenVOInterval;
|
|
}
|
|
break;
|
|
case "seeder":
|
|
level notify("dlc_vo_notify","inbound_seeder", alien);
|
|
break;
|
|
|
|
case "brute":
|
|
level notify("dlc_vo_notify","inbound_brute", alien);
|
|
break;
|
|
|
|
case "mammoth":
|
|
level notify("dlc_vo_notify","inbound_mammoth", alien);
|
|
break;
|
|
|
|
case "gargoyle":
|
|
level notify("dlc_vo_notify","inbound_gargoyle", alien);
|
|
break;
|
|
|
|
case "bomber":
|
|
level notify("dlc_vo_notify","inbound_bomber", alien);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/#
|
|
debug_spawn_info( spawn_time, spawn_method, requested_amount, modified_amount, spawn_type, actual_amount )
|
|
{
|
|
if ( IsDefined(spawn_method) )
|
|
spawnTypeDebug = spawn_method + "=";
|
|
else
|
|
spawnTypeDebug = "Spawn=";
|
|
|
|
msg = spawnTypeDebug + requested_amount + " " + spawn_type + " Modify=" + modified_amount;
|
|
if ( IsDefined( spawn_time ) )
|
|
msg = msg + " Time=" + spawn_time;
|
|
|
|
if ( IsDefined( actual_amount ) )
|
|
msg = msg + " Spawned=" + actual_amount;
|
|
|
|
totalSpawned = get_current_agent_count() - level.pending_meteor_spawns - level.pending_ground_spawns - level.pending_custom_spawns;
|
|
msg = msg + " Total=" + totalSpawned;
|
|
|
|
debug_print( msg );
|
|
}
|
|
#/
|
|
|
|
get_scaled_alien_amount( desired_amount )
|
|
{
|
|
scaledAmount = desired_amount * get_current_spawn_count_multiplier();
|
|
|
|
return max( 1, int( scaledAmount + 0.5 ) ); // round up
|
|
}
|
|
|
|
get_modified_alien_amount( desired_amount, ai_type, spawn_method )
|
|
{
|
|
scaledAmount = get_scaled_alien_amount( desired_amount );
|
|
|
|
return get_max_type_count( ai_type, scaledAmount );
|
|
}
|
|
|
|
spawn_type( ai_type, spawn_method, override_lanes )
|
|
{
|
|
if ( !IsDefined( ai_type.max_spawned ) )
|
|
desiredSpawnAmount = ai_type.min_spawned;
|
|
else
|
|
desiredSpawnAmount = RandomIntRange( ai_type.min_spawned, ai_type.max_spawned );
|
|
modifiedDesiredSpawnAmount = get_modified_alien_amount( desiredSpawnAmount, ai_type, spawn_method );
|
|
|
|
spawnedAliens = [];
|
|
|
|
if ( !IsDefined( level.cycle_data.current_lane ) && !IsDefined( level.cycle_data.non_lane_spawn_started ) )
|
|
{
|
|
level thread maps\mp\alien\_music_and_dialog::play2DSpawnSound();
|
|
level.cycle_data.non_lane_spawn_started = true;
|
|
}
|
|
|
|
//level notify( "alien_type_spawned", ai_type.type_name );
|
|
|
|
switch( ai_type.type_name )
|
|
{
|
|
case "minion":
|
|
spawn_minions( modifiedDesiredSpawnAmount );
|
|
break;
|
|
case "elite":
|
|
spawn_elite( modifiedDesiredSpawnAmount );
|
|
break;
|
|
default:
|
|
spawnedAliens = spawn_aliens( modifiedDesiredSpawnAmount, ai_type.type_name, override_lanes );
|
|
break;
|
|
}
|
|
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
if ( IsDefined( level.current_cycle ) )
|
|
spawnTime = int( level.current_intensity * level.current_cycle.fullIntensityTime * 0.001 );
|
|
else
|
|
spawnTime = undefined;
|
|
|
|
debug_spawn_info( spawnTime, spawn_method, desiredSpawnAmount, modifiedDesiredSpawnAmount, ai_type.type_name, spawnedAliens.size );
|
|
}
|
|
#/
|
|
|
|
return spawnedAliens;
|
|
}
|
|
|
|
get_max_alien_count()
|
|
{
|
|
return level.cycle_data.max_alien_count;
|
|
}
|
|
|
|
get_max_type_count( ai_type, desiredAmount )
|
|
{
|
|
if ( !IsDefined( level.current_cycle ) )
|
|
return desiredAmount;
|
|
|
|
typeName = get_translated_ai_type( ai_type.type_name );
|
|
|
|
if ( IsDefined( ai_type.max_of_type ) )
|
|
{
|
|
if ( ai_type.max_of_type <= 0 )
|
|
level.current_cycle.type_max_counts[typeName] = undefined;
|
|
else
|
|
level.current_cycle.type_max_counts[typeName] = ai_type.max_of_type;
|
|
}
|
|
|
|
if ( !IsDefined( level.current_cycle.type_max_counts[typeName] ) )
|
|
return desiredAmount;
|
|
|
|
currentAllowedCount = get_scaled_alien_amount( level.current_cycle.type_max_counts[typeName] );
|
|
currentAllowedCount = Max( 0, currentAllowedCount - get_current_agent_count_of_type( typeName ) );
|
|
|
|
return Min( desiredAmount, currentAllowedCount );
|
|
}
|
|
|
|
at_max_alien_count()
|
|
{
|
|
return ( get_current_agent_count() >= get_max_alien_count() );
|
|
}
|
|
|
|
get_translated_ai_type( ai_type )
|
|
{
|
|
switch( ai_type )
|
|
{
|
|
case "minion_nometeor":
|
|
return "minion";
|
|
}
|
|
|
|
return ai_type;
|
|
}
|
|
|
|
spawn_aliens( desired_amount, ai_type, override_lanes )
|
|
{
|
|
ai_type = get_translated_ai_type( ai_type );
|
|
spawnedAliens = [];
|
|
for ( spawnIndex = 0; spawnIndex < desired_amount; spawnIndex++ )
|
|
{
|
|
// TODO: Possibly rework to spread the spawn out amongst types if we're close to the max count when respawning so at least one of each type is spawned
|
|
if ( at_max_alien_count() )
|
|
{
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
numNotSpawned = desired_amount - spawnIndex;
|
|
debug_print( "Max alien count hit. " + numNotSpawned + " " + ai_type + " not spawned." );
|
|
}
|
|
#/
|
|
break;
|
|
}
|
|
|
|
sort_func = undefined;
|
|
|
|
if ( is_chaos_mode() )
|
|
sort_func = ::sort_player_view_direction;
|
|
|
|
alien = spawn_alien( ai_type, override_lanes, undefined, sort_func );
|
|
|
|
spawnedAliens[spawnedAliens.size] = alien;
|
|
|
|
wait level.cycle_data.min_spawn_interval;
|
|
}
|
|
|
|
return spawnedAliens;
|
|
}
|
|
|
|
spawn_minions( desired_amount )
|
|
{
|
|
level thread spawn_meteor_aliens( desired_amount, "minion" );
|
|
wait level.cycle_data.min_spawn_interval;
|
|
}
|
|
|
|
spawn_elite( desired_amount )
|
|
{
|
|
level thread spawn_ground_aliens( desired_amount, "elite", ::elite_monitor );
|
|
wait level.cycle_data.min_spawn_interval;
|
|
}
|
|
|
|
elite_monitor()
|
|
{
|
|
if ( !IsDefined( level.cycle_data.elite_count ) || level.cycle_data.elite_count == 0 )
|
|
{
|
|
level thread maps\mp\alien\_music_and_dialog::handleFirstEliteArrival( self );
|
|
level.cycle_data.elite_count = 1;
|
|
}
|
|
else
|
|
{
|
|
level.cycle_data.elite_count++;
|
|
}
|
|
|
|
self waittill( "death" );
|
|
level.cycle_data.elite_count--;
|
|
|
|
if ( level.cycle_data.elite_count == 0 )
|
|
level thread maps\mp\alien\_music_and_dialog::handleLastEliteDeath( self );
|
|
}
|
|
|
|
is_spawn_debug_info_requested()
|
|
{
|
|
return GetDvarInt( "scr_alienspawninfo", 0 ) == 1 || GetDvarInt( "scr_alienspawneventinfo" ) == 1;
|
|
}
|
|
|
|
spawn_meteor_aliens( desired_spawn_amount, alien_type )
|
|
{
|
|
level endon( "game_ended" );
|
|
level endon ( "nuke_went_off" );
|
|
|
|
if ( desired_spawn_amount >= 9 )
|
|
alien_per_meteoroid = int( desired_spawn_amount / 3 );
|
|
else
|
|
alien_per_meteoroid = 4;
|
|
|
|
level.pending_meteor_spawns += desired_spawn_amount;
|
|
|
|
while ( desired_spawn_amount > 0 )
|
|
{
|
|
count = alien_per_meteoroid;
|
|
if ( desired_spawn_amount < alien_per_meteoroid )
|
|
count = desired_spawn_amount;
|
|
|
|
earlyFail = level thread maps\mp\alien\_spawnlogic::spawn_alien_meteoroid( alien_type, count );
|
|
if ( IsDefined( earlyFail ) )
|
|
{
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
debug_print( "Meteor creation failed! " + desired_spawn_amount + " minions not spawned!" );
|
|
}
|
|
#/
|
|
|
|
level.pending_meteor_spawns -= desired_spawn_amount;
|
|
return;
|
|
}
|
|
|
|
desired_spawn_amount -= count;
|
|
|
|
// random wait
|
|
wait RandomIntRange( 5, 10 );
|
|
level thread maps\mp\alien\_music_and_dialog::playVOForMinions();
|
|
}
|
|
}
|
|
|
|
spawn_ground_aliens( desired_spawn_amount, alien_type, spawn_notify_func )
|
|
{
|
|
level endon( "game_ended" );
|
|
level endon( "nuke_went_off" );
|
|
|
|
MIN_INTERVAL = 2.0;
|
|
MAX_INTERVAL = 3.5;
|
|
|
|
while ( desired_spawn_amount > 0 )
|
|
{
|
|
earlyFail = level thread spawn_ground_alien( alien_type, 1, spawn_notify_func );
|
|
if ( IsDefined( earlyFail ) )
|
|
{
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
debug_print( "Ground spawn point failed! " + desired_spawn_amount + " queens not spawned!" );
|
|
}
|
|
#/
|
|
return;
|
|
}
|
|
|
|
level.pending_ground_spawns++;
|
|
desired_spawn_amount -= 1;
|
|
|
|
// random wait
|
|
wait RandomFloatRange( MIN_INTERVAL, MAX_INTERVAL );
|
|
//level thread maps\mp\alien\_music_and_dialog::playVOForMinions();
|
|
}
|
|
}
|
|
|
|
spawn_ground_alien( alien_type, spawn_count, spawn_notify_func )
|
|
{
|
|
GROUND_ALIEN_LAST_USED_DURATION = 10000; // ms
|
|
overrideLanes = [];
|
|
|
|
alien = spawn_alien( alien_type, overrideLanes, GROUND_ALIEN_LAST_USED_DURATION, ::sort_closest_to_players );
|
|
alien thread [[spawn_notify_func]]();
|
|
|
|
level notify( "ground_alien_spawned", alien );
|
|
}
|
|
|
|
monitor_meteor_spawn()
|
|
{
|
|
level endon( "game_ended" );
|
|
level endon ( "nuke_went_off" );
|
|
|
|
while ( true )
|
|
{
|
|
level waittill( "meteor_aliens_spawned", aliens, requestedCount );
|
|
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
totalSpawned = get_current_agent_count() - level.pending_meteor_spawns - level.pending_ground_spawns + aliens.size;
|
|
debug_print( aliens.size + " minions spawned! " + totalSpawned + " alive aliens total." );
|
|
}
|
|
#/
|
|
|
|
level.pending_meteor_spawns -= requestedCount;
|
|
if ( level.pending_meteor_spawns < 0 )
|
|
level.pending_meteor_spawns = 0;
|
|
}
|
|
}
|
|
|
|
monitor_ground_spawn()
|
|
{
|
|
level endon( "game_ended" );
|
|
level endon ( "nuke_went_off" );
|
|
|
|
while ( true )
|
|
{
|
|
level waittill( "ground_alien_spawned", alien );
|
|
alienType = alien maps\mp\alien\_utility::get_alien_type();
|
|
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
{
|
|
totalSpawned = get_current_agent_count() - level.pending_meteor_spawns - level.pending_ground_spawns + 1;
|
|
debug_print( 1 + " " + alienType + " spawned! " + totalSpawned + " alive aliens total." );
|
|
}
|
|
#/
|
|
|
|
if ( !IsAlive( alien ) )
|
|
continue;
|
|
|
|
level.pending_ground_spawns--;
|
|
if ( level.pending_ground_spawns < 0 )
|
|
level.pending_ground_spawns = 0;
|
|
}
|
|
}
|
|
|
|
get_current_agent_count( exclude_meteor, exclude_ground, exclude_pending )
|
|
{
|
|
currentCount = maps\mp\agents\_agent_utility::getNumActiveAgents();
|
|
|
|
if ( !IsDefined( exclude_meteor ) || !exclude_meteor )
|
|
currentCount += level.pending_meteor_spawns;
|
|
|
|
if ( !IsDefined( exclude_ground ) || !exclude_ground )
|
|
currentCount += level.pending_ground_spawns;
|
|
|
|
if ( !IsDefined( exclude_pending ) || !exclude_pending )
|
|
currentCount += level.pending_custom_spawns;
|
|
|
|
return currentCount;
|
|
}
|
|
|
|
get_current_agent_count_of_type( alien_type )
|
|
{
|
|
agents = maps\mp\agents\_agent_utility::getActiveAgentsOfType( "alien" );
|
|
typeCount = 0;
|
|
|
|
foreach ( ai in agents )
|
|
{
|
|
if ( IsDefined( ai.pet ) && ai.pet )
|
|
continue;
|
|
|
|
if ( ai get_alien_type() == alien_type )
|
|
typeCount++;
|
|
}
|
|
|
|
return typeCount;
|
|
}
|
|
|
|
spawn_alien( ai_type, override_lanes, override_last_time_used_duration, override_sort_func )
|
|
{
|
|
spawnPointInfo = find_safe_spawn_point_info( ai_type, override_lanes, override_last_time_used_duration, override_sort_func );
|
|
spawnPoint = spawnPointInfo[ "node" ];
|
|
|
|
// blocker hive spitter attack chopper logic
|
|
new_alien_to_attack_the_chopper = false; //%
|
|
if ( ai_type == "spitter" && isdefined( level.hive_heli ) && level.hive_heli.health > 0 && ThreatBiasGroupExists( "spitters" ) )
|
|
{
|
|
against_players = 0;
|
|
against_chopper = 0;
|
|
foreach ( agent in level.agentArray )
|
|
{
|
|
if ( !IsDefined( agent.isActive ) || !agent.isActive || !isalive( agent ) || !isdefined( agent.alien_type ) || agent.alien_type != "spitter" )
|
|
continue;
|
|
|
|
if ( agent GetThreatBiasGroup() == "spitters" )
|
|
against_chopper++;
|
|
else
|
|
against_players++;
|
|
}
|
|
|
|
total = int( max ( 1, against_chopper + against_players ) );
|
|
level.spitters_against_players_ratio = against_players / total;
|
|
|
|
// ===== if players are too far from chopper, we will have every spitter attack chopper ======
|
|
total_dist = 0;
|
|
foreach ( player in level.players )
|
|
total_dist += distance2D( player.origin, level.hive_heli.origin );
|
|
|
|
avg_dist = total_dist/int( min( 1, level.players.size ) );
|
|
|
|
chance = 0.32;
|
|
if ( avg_dist > 1500 )
|
|
chance = 0.1;
|
|
//============================================================================================
|
|
|
|
if ( level.spitters_against_players_ratio >= chance )
|
|
new_alien_to_attack_the_chopper = true;
|
|
}
|
|
|
|
intro_vignette_ai_type = process_intro_vignette_ai_type( ai_type );
|
|
|
|
if ( isDefined( spawnPoint.script_noteworthy ) )
|
|
introVignetteAnim = level.cycle_data.spawn_node_info [ spawnPoint.script_noteworthy ].vignetteInfo[ intro_vignette_ai_type ];
|
|
else
|
|
introVignetteAnim = undefined;
|
|
|
|
alien = process_spawn( ai_type, spawnPoint, introVignetteAnim );
|
|
|
|
if (( ai_type == "spitter" || ai_type == "seeder" ) && isdefined( level.hive_heli ) && level.hive_heli.health > 0 && ThreatBiasGroupExists( "spitters" ) )
|
|
{
|
|
if ( new_alien_to_attack_the_chopper )
|
|
alien maps\mp\alien\_spawnlogic::make_spitter_attack_chopper( true );
|
|
else
|
|
alien maps\mp\alien\_spawnlogic::make_spitter_attack_chopper( false );
|
|
}
|
|
|
|
if ( ( ai_type == "spitter" || ai_type == "seeder" || ai_type == "gargoyle" ) && flag_exist( "player_using_vanguard" ) && flag( "player_using_vanguard" ) && ThreatBiasGroupExists( "spitters" ) )
|
|
{
|
|
alien SetThreatBiasGroup( "spitters" );
|
|
alien.favoriteenemy = level.alien_vanguard;
|
|
}
|
|
|
|
if ( isDefined( spawnPoint.script_noteworthy ) )
|
|
{
|
|
if(IsSubStr(spawnPoint.script_noteworthy, "vent"))
|
|
level notify("dlc_vo_notify","direction_vo", "spawn_vent");
|
|
if(IsSubStr(spawnPoint.script_noteworthy, "duct"))
|
|
level notify("dlc_vo_notify","direction_vo", "spawn_vent");
|
|
if(IsSubStr(spawnPoint.script_noteworthy, "grate"))
|
|
level notify("dlc_vo_notify","direction_vo", "spawn_grate");
|
|
}
|
|
|
|
level notify ( "spawned_alien", alien );
|
|
return alien;
|
|
}
|
|
|
|
process_intro_vignette_ai_type( ai_type )
|
|
{
|
|
switch ( ai_type )
|
|
{
|
|
case "leper":
|
|
case "locust":
|
|
return "brute";
|
|
|
|
case "mammoth":
|
|
return "elite";
|
|
}
|
|
|
|
return ai_type;
|
|
}
|
|
|
|
process_spawn( spawn_type, spawn_point, intro_vignette_anim )
|
|
{
|
|
spawnAngles = ( 0, 0, 0 );
|
|
if ( IsDefined( spawn_point.angles ) )
|
|
spawnAngles = spawn_point.angles;
|
|
|
|
spawn_point.last_used_time = GetTime();
|
|
|
|
spawnType = "wave" + " " + spawn_type;
|
|
alien = maps\mp\gametypes\aliens::addAlienAgent( "axis", spawn_point.origin, spawnAngles, spawnType, intro_vignette_anim );
|
|
|
|
return alien;
|
|
}
|
|
|
|
find_safe_spawn_point_info( type_name, override_lanes, override_last_time_used_duration, override_sort_func )
|
|
{
|
|
if ( should_lane_spawn( override_lanes, type_name ) )
|
|
return find_lane_spawn_node( override_lanes );
|
|
|
|
playerVolumes = get_current_player_volumes();
|
|
|
|
if ( playerVolumes.size == 0 )
|
|
{
|
|
// temp fallback if nobody is in a volume. Will be updated, probably to pick spot around guy who needs a close spawn the most
|
|
spawnLocationInfo = find_random_spawn_node( level.cycle_data.spawner_list, type_name, override_last_time_used_duration, ::filter_spawn_point_by_distance_from_player, undefined, override_sort_func );
|
|
return spawnLocationInfo;
|
|
}
|
|
else if ( playerVolumes.size == 1 )
|
|
{
|
|
foreach ( index, player in playerVolumes )
|
|
{
|
|
if ( level.cycle_data.spawn_zones[index].spawn_nodes.size == 0 )
|
|
continue;
|
|
|
|
spawnLocationInfo = find_random_spawn_node( level.cycle_data.spawn_zones[index].spawn_nodes, type_name, override_last_time_used_duration, undefined, undefined, override_sort_func );
|
|
return spawnLocationInfo;
|
|
}
|
|
}
|
|
|
|
return find_safe_spawn_spot_with_volumes( playerVolumes, type_name, override_last_time_used_duration, override_sort_func );
|
|
}
|
|
|
|
get_current_encounter()
|
|
{
|
|
return level.encounter_name;
|
|
}
|
|
|
|
should_lane_spawn( override_lanes, type_name )
|
|
{
|
|
OFFLINE_DRILL_MAX_AVERAGE_DISTANCE = 1024;
|
|
|
|
if ( is_chaos_mode() )
|
|
return false;
|
|
|
|
if ( IsDefined( override_lanes ) && override_lanes.size == 0)
|
|
return false;
|
|
|
|
if ( !can_spawn_at_any_node( type_name ) )
|
|
return false;
|
|
|
|
if ( isDefined( level.blocker_hive_active ) && level.blocker_hive_active )
|
|
return true;
|
|
|
|
drillState = get_current_drill_state();
|
|
|
|
if ( !IsDefined( override_lanes ) )
|
|
{
|
|
if ( !IsDefined( level.cycle_data.current_lane ) )
|
|
return false;
|
|
|
|
if ( drillState == "idle" )
|
|
return false;
|
|
}
|
|
|
|
if ( drillState == "offline" )
|
|
{
|
|
total_dist = 0;
|
|
player_count = 0;
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( isdefined( player ) && isalive( player ) )
|
|
{
|
|
player_count++;
|
|
total_dist += distance2D( player.origin, level.drill.origin );
|
|
}
|
|
}
|
|
|
|
if ( player_count > 0)
|
|
{
|
|
if ( total_dist / player_count > OFFLINE_DRILL_MAX_AVERAGE_DISTANCE )
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
get_current_drill_state()
|
|
{
|
|
if ( !IsDefined( level.drill ) || !IsDefined( level.drill.state ) )
|
|
return "undefined";
|
|
|
|
return level.drill.state;
|
|
}
|
|
|
|
get_override_lane_entry( override_lanes )
|
|
{
|
|
/# Assert( override_lanes.size > 0 ); #/
|
|
|
|
if ( !IsDefined( level.override_lane_index ) )
|
|
return get_random_entry( override_lanes );
|
|
|
|
if ( level.override_lane_index >= override_lanes.size )
|
|
level.override_lane_index = 0;
|
|
|
|
currentIndex = level.override_lane_index;
|
|
level.override_lane_index++;
|
|
|
|
return override_lanes[currentIndex];
|
|
}
|
|
|
|
find_lane_spawn_node( override_lanes )
|
|
{
|
|
if ( IsDefined( override_lanes ) && override_lanes.size > 0 )
|
|
{
|
|
laneIndex = get_random_entry( override_lanes );
|
|
spawnIndex = get_override_lane_entry( level.lanes[laneIndex] );
|
|
}
|
|
else
|
|
{
|
|
laneIndex = get_random_entry( level.cycle_data.current_lane.lanes );
|
|
spawnIndex = level.lanes[laneIndex][ RandomInt( level.lanes[laneIndex].size ) ];
|
|
}
|
|
|
|
/#
|
|
if ( is_spawn_debug_info_requested() )
|
|
PrintLn( "Lane spawning: " + laneIndex );
|
|
#/
|
|
|
|
nodeInfo = [];
|
|
nodeInfo[ "node" ] = level.cycle_data.spawner_list[spawnIndex]["location"];
|
|
nodeInfo[ "validNode" ] = true;
|
|
|
|
return nodeInfo;
|
|
}
|
|
|
|
get_current_encounter_lane_index()
|
|
{
|
|
endIndex = undefined;
|
|
currentEncounter = get_current_encounter();
|
|
if ( !IsDefined( currentEncounter ) )
|
|
return undefined;
|
|
|
|
foreach ( index, lane in level.encounter_lanes[currentEncounter] )
|
|
{
|
|
laneIntensityThreshold = lane.activation_time / level.current_cycle.fullIntensityTime;
|
|
if ( laneIntensityThreshold > level.current_intensity )
|
|
break;
|
|
|
|
endIndex = index;
|
|
}
|
|
|
|
return level.encounter_lanes[currentEncounter][endIndex];
|
|
}
|
|
|
|
get_random_entry( array )
|
|
{
|
|
entryCount = -1;
|
|
randomEntry = RandomInt( array.size );
|
|
|
|
foreach ( index, entry in array )
|
|
{
|
|
entryCount++;
|
|
if ( entryCount == randomEntry )
|
|
return array[index];
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
filter_spawn_point_by_distance_from_player( spawnNode, spawnType )
|
|
{
|
|
if ( spawnType == "elite" )
|
|
return true;
|
|
|
|
testLocation = level.players[0].origin;
|
|
return DistanceSquared( testLocation, spawnNode.origin ) > level.cycle_data.safe_spawn_distance_sq;
|
|
}
|
|
|
|
sort_closest_to_players( array )
|
|
{
|
|
return sort_array( array, ::sort_closest_distance_to_players_func, 0 );
|
|
}
|
|
|
|
sort_player_view_direction( node_array )
|
|
{
|
|
random_player = level.players[randomInt(level.players.size)];
|
|
player_view_dir = anglesToForward( random_player GetPlayerAngles() );
|
|
|
|
return sort_array( node_array, ::sort_player_view_direction_func, 0, [ random_player.origin, player_view_dir ] );
|
|
}
|
|
|
|
find_random_spawn_node( spawn_nodes, type_name, override_last_time_used_duration, filter_func, filter_optional_param, sort_func )
|
|
{
|
|
/#
|
|
if ( level.debug_spawn_director_active )
|
|
return debug_find_spawn_node( spawn_nodes );
|
|
#/
|
|
|
|
if ( IsDefined( override_last_time_used_duration ) )
|
|
lastUsedDuration = override_last_time_used_duration;
|
|
else
|
|
lastUsedDuration = level.cycle_data.spawn_point_last_used_duration;
|
|
|
|
if ( !IsDefined ( sort_func ) )
|
|
sort_func = ::array_randomize;
|
|
|
|
spawn_nodes = [[sort_func]]( spawn_nodes );
|
|
bestIndex = 0;
|
|
minDesiredTimeSinceUsed = RandomFloatRange( lastUsedDuration * 0.5, lastUsedDuration * 0.75 );
|
|
bestTimeSinceUsed = 0.0;
|
|
foundValidNode = false;
|
|
|
|
for ( nodeIndex = 0; nodeIndex < spawn_nodes.size; nodeIndex++ )
|
|
{
|
|
spawnNode = spawn_nodes[nodeIndex]["location"];
|
|
|
|
if ( IsDefined( filter_func ) && !passes_spawn_node_filter( spawnNode, type_name, filter_func, filter_optional_param ) )
|
|
continue;
|
|
|
|
if ( IsDefined( spawn_nodes[nodeIndex]["location"].script_linkName ) )
|
|
spawnerName = spawn_nodes[nodeIndex]["location"].script_linkName;
|
|
else
|
|
spawnerName = undefined;
|
|
|
|
if ( !is_valid_spawn_node_for_type ( spawn_nodes[nodeIndex]["types"], type_name, spawnerName ) )
|
|
continue;
|
|
|
|
if ( !IsDefined( spawnNode.last_used_time ) )
|
|
{
|
|
foundValidNode = true;
|
|
bestIndex = nodeIndex;
|
|
break;
|
|
}
|
|
|
|
timeSinceUsed = GetTime() - spawnNode.last_used_time;
|
|
|
|
if ( timeSinceUsed > minDesiredTimeSinceUsed )
|
|
{
|
|
foundValidNode = true;
|
|
bestIndex = nodeIndex;
|
|
break;
|
|
}
|
|
|
|
if ( timeSinceUsed > bestTimeSinceUsed )
|
|
{
|
|
bestTimeSinceUsed = timeSinceUsed;
|
|
bestIndex = nodeIndex;
|
|
}
|
|
}
|
|
|
|
nodeInfo = [];
|
|
nodeInfo[ "node" ] = spawn_nodes[bestIndex]["location"];
|
|
nodeInfo[ "validNode" ] = foundValidNode;
|
|
|
|
return nodeInfo;
|
|
}
|
|
|
|
/#
|
|
debug_find_spawn_node( spawn_nodes )
|
|
{
|
|
if ( level.debug_spawn_director_spawn_index >= spawn_nodes.size )
|
|
level.debug_spawn_director_spawn_index = 0;
|
|
|
|
nodeInfo = [];
|
|
nodeInfo[ "node" ] = spawn_nodes[level.debug_spawn_director_spawn_index]["location"];
|
|
nodeInfo[ "validNode" ] = true;
|
|
level.debug_spawn_director_spawn_index++;
|
|
|
|
return nodeInfo;
|
|
}
|
|
#/
|
|
|
|
is_valid_spawn_node_for_type ( valid_types, type_name, spawner_name )
|
|
{
|
|
if ( valid_types.size == 0 )
|
|
return can_spawn_at_any_node( type_name );
|
|
|
|
foreach ( ai_type in valid_types )
|
|
{
|
|
if ( ai_type == type_name )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
can_spawn_at_any_node( type_name )
|
|
{
|
|
if( isDefined( level.dlc_alien_type_node_match_override_func ) )
|
|
{
|
|
canSpawnAnyNode = self [[level.dlc_alien_type_node_match_override_func]]( type_name );
|
|
if ( IsDefined( canSpawnAnyNode ) )
|
|
return canSpawnAnyNode;
|
|
}
|
|
|
|
switch ( type_name )
|
|
{
|
|
case "elite":
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
passes_spawn_node_filter( spawn_node, spawn_type, filter_func, filter_optional_param )
|
|
{
|
|
if ( IsDefined( filter_optional_param ) )
|
|
return [[ filter_func ]]( spawn_node, spawn_type, filter_optional_param );
|
|
|
|
return [[ filter_func ]]( spawn_node, spawn_type );
|
|
}
|
|
|
|
find_safe_spawn_spot_with_volumes( player_volumes, type_name, override_last_used_spawn_duration, override_sort_func )
|
|
{
|
|
sortedVolumes = score_and_sort_spawn_zones( player_volumes );
|
|
|
|
foreach ( zone in sortedVolumes )
|
|
{
|
|
if ( level.cycle_data.spawn_zones[zone.name].spawn_nodes.size == 0 )
|
|
continue;
|
|
|
|
playersToTest = [];
|
|
foreach( index, zoneVolume in player_volumes )
|
|
{
|
|
if ( index != zone.name )
|
|
playersToTest = array_combine( playersToTest, zoneVolume.players );
|
|
}
|
|
|
|
spawnLocationInfo = find_random_spawn_node( level.cycle_data.spawn_zones[zone.name].spawn_nodes, type_name, override_last_used_spawn_duration, ::is_safe_spawn_location, playersToTest, override_sort_func );
|
|
if ( spawnLocationInfo[ "validNode" ] )
|
|
return spawnLocationInfo;
|
|
|
|
// TODO: Possibly search outwards for more nodes to try in order to force the higher scored zone to be used before moving to next zone
|
|
}
|
|
|
|
// temp fallback, return random
|
|
spawnLocationInfo = find_random_spawn_node( level.cycle_data.spawner_list, type_name, override_last_used_spawn_duration, ::filter_spawn_point_by_distance_from_player, undefined, override_sort_func );
|
|
return spawnLocationInfo;
|
|
}
|
|
|
|
score_and_sort_spawn_zones( spawn_zones )
|
|
{
|
|
playerProximity = calculate_player_proximity_scores();
|
|
|
|
foreach ( zone in spawn_zones )
|
|
{
|
|
zoneScore = 0.0;
|
|
|
|
foreach( player in zone.players )
|
|
{
|
|
zoneScore += level.cycle_data.player_in_zone_score_modifier;
|
|
if ( IsDefined( player.current_attackers ) )
|
|
zoneScore -= player.current_attackers.size * level.cycle_data.current_attacker_in_zone_score_modifier;
|
|
|
|
if ( IsDefined( playerProximity[player.name] ) && ( playerProximity[player.name] > level.cycle_data.group_break_away_distance_sq) )
|
|
zoneScore += min( level.cycle_data.max_break_away_zone_score_increase, playerProximity[player.name] / level.cycle_data.group_break_away_distance_sq );
|
|
}
|
|
|
|
zoneScore -= get_number_of_recently_used_spawn_points_in_zone( zone ) * level.cycle_data.recently_used_spawn_zone_score_modifier;
|
|
|
|
// TODO: Possibly slightly increment score based on number of available spawn nodes for that zone
|
|
|
|
zone.zone_score = zoneScore;
|
|
}
|
|
|
|
indexedArray = [];
|
|
foreach ( zone in spawn_zones )
|
|
indexedArray[indexedArray.size] = zone;
|
|
|
|
return sort_array( indexedArray, ::sort_zone_score_func );
|
|
}
|
|
|
|
calculate_player_proximity_scores()
|
|
{
|
|
playerProximity = [];
|
|
|
|
if ( level.players.size <= 2 )
|
|
return playerProximity;
|
|
|
|
for ( playerIndex = 0; playerIndex < level.players.size; playerIndex++ )
|
|
{
|
|
playerName = level.players[playerIndex].name;
|
|
if ( !IsDefined( playerProximity[playerName] ) )
|
|
playerProximity[playerName] = 99999999.0;
|
|
|
|
for ( closePlayerIndex = playerIndex + 1; closePlayerIndex < level.players.size; closePlayerIndex++ )
|
|
{
|
|
distanceSq = DistanceSquared( level.players[playerIndex].origin, level.players[closePlayerIndex].origin );
|
|
if ( distanceSq < playerProximity[playerName])
|
|
playerProximity[playerName] = distanceSq;
|
|
|
|
closePlayerName = level.players[closePlayerIndex].name;
|
|
if ( !IsDefined( playerProximity[closePlayerName] ) || distanceSq < playerProximity[closePlayerName] )
|
|
playerProximity[closePlayerName] = distanceSq;
|
|
}
|
|
}
|
|
|
|
return playerProximity;
|
|
}
|
|
|
|
get_number_of_recently_used_spawn_points_in_zone( zone )
|
|
{
|
|
recentlyUsedCount = 0;
|
|
currentTime = GetTime();
|
|
|
|
for ( nodeIndex = 0; nodeIndex < level.cycle_data.spawn_zones[zone.name].spawn_nodes.size; nodeIndex++ )
|
|
{
|
|
spawnPoint = level.cycle_data.spawn_zones[zone.name].spawn_nodes[nodeIndex]["location"];
|
|
if ( IsDefined( spawnPoint.last_used_time ) )
|
|
{
|
|
elapsedTime = currentTime - spawnPoint.last_used_time;
|
|
if ( elapsedTime < level.cycle_data.spawn_point_last_used_duration )
|
|
recentlyUsedCount++;
|
|
}
|
|
}
|
|
|
|
return recentlyUsedCount;
|
|
}
|
|
|
|
sort_zone_score_func( test_entry, base_entry, pass_through_parameter_list )
|
|
{
|
|
return test_entry.zone_score > base_entry.zone_score;
|
|
}
|
|
|
|
is_safe_spawn_location( spawnLocation, spawnType, playersToTest )
|
|
{
|
|
if ( spawnType == "elite" )
|
|
return true;
|
|
|
|
foreach ( player in playersToTest )
|
|
{
|
|
if ( DistanceSquared( spawnLocation.origin, player.origin ) > level.cycle_data.safe_spawn_distance_sq )
|
|
continue;
|
|
|
|
if ( has_line_of_sight( spawnLocation.origin, player.origin ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
has_line_of_sight( spawnLocation, playerLocation )
|
|
{
|
|
while ( max_traces_reached() )
|
|
wait 0.05;
|
|
|
|
level.spawn_node_traces_this_frame++;
|
|
|
|
return BulletTracePassed( spawnLocation, playerLocation, false, undefined );
|
|
}
|
|
|
|
get_current_player_volumes()
|
|
{
|
|
// Level specific override
|
|
if ( isDefined( level.dlc_get_current_player_volumes_override_func ) )
|
|
{
|
|
return level [[level.dlc_get_current_player_volumes_override_func ]]();
|
|
}
|
|
|
|
spawnZoneVolumes = [];
|
|
currentVolumes = [];
|
|
|
|
foreach ( zone in level.cycle_data.spawn_zones )
|
|
{
|
|
spawnZoneVolumes[spawnZoneVolumes.size] = zone.volume;
|
|
}
|
|
|
|
foreach ( player in level.players )
|
|
{
|
|
if ( !isAlive( player ) || player.sessionstate == "spectator" )
|
|
continue;
|
|
|
|
playerCurrentVolumes = player GetIsTouchingEntities( spawnZoneVolumes );
|
|
foreach ( volume in playerCurrentVolumes )
|
|
{
|
|
entryIndex = 0;
|
|
if ( !IsDefined( currentVolumes[volume.script_linkName] ) )
|
|
{
|
|
volumeName = volume.script_linkName;
|
|
currentVolumes[volumeName] = SpawnStruct();
|
|
currentVolumes[volumeName].players = [];
|
|
currentVolumes[volumeName].origin = volume.origin;
|
|
currentVolumes[volumeName].name = volumeName;
|
|
}
|
|
|
|
entryIndex = currentVolumes[volume.script_linkName].players.size;
|
|
currentVolumes[volume.script_linkName].players[entryIndex] = player;
|
|
}
|
|
}
|
|
|
|
return currentVolumes;
|
|
}
|
|
|
|
max_traces_reached()
|
|
{
|
|
MAX_TRACE_COUNT_PER_FRAME = 5;
|
|
if ( gettime() > level.spawn_node_traces_frame_time )
|
|
{
|
|
level.spawn_node_traces_frame_time = gettime();
|
|
level.spawn_node_traces_this_frame = 0;
|
|
}
|
|
|
|
return level.spawn_node_traces_this_frame >= MAX_TRACE_COUNT_PER_FRAME;
|
|
}
|
|
|
|
// Cycle Scalars
|
|
load_cycle_scalars_from_table( cycle_data )
|
|
{
|
|
cycle_data.cycle_scalars = [];
|
|
|
|
if( is_casual_mode() )
|
|
{
|
|
level.cycle_health_scalar = level.casual_health_scalar;
|
|
level.cycle_damage_scalar = level.casual_damage_scalar;
|
|
level.cycle_reward_scalar = level.casual_reward_scalar;
|
|
level.cycle_score_scalar = level.casual_score_scalar;
|
|
}
|
|
else if( is_hardcore_mode() )
|
|
{
|
|
level.cycle_health_scalar = level.hardcore_health_scalar;
|
|
level.cycle_damage_scalar = level.hardcore_damage_scalar;
|
|
level.cycle_reward_scalar = level.hardcore_reward_scalar;
|
|
level.cycle_score_scalar = level.hardcore_score_scalar;
|
|
}
|
|
else
|
|
{
|
|
level.cycle_health_scalar = 1.0;
|
|
level.cycle_damage_scalar = 1.0;
|
|
level.cycle_reward_scalar = 1.0;
|
|
level.cycle_score_scalar = 1.0;
|
|
}
|
|
|
|
|
|
for ( entryIndex = TABLE_CYCLE_SCALAR_START_INDEX; entryIndex <= TABLE_CYCLE_SCALAR_END_INDEX; entryIndex++ )
|
|
{
|
|
cycle = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_SCALAR_CYCLE );
|
|
if ( cycle == "" )
|
|
break;
|
|
cycle = int( cycle );
|
|
cycleScalars = SpawnStruct();
|
|
cycleScalars.health = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_SCALAR_HEALTH ) );
|
|
cycleScalars.damage = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_SCALAR_DAMAGE ) );
|
|
cycleScalars.reward = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_SCALAR_REWARD ) );
|
|
cycleScalars.score = float( tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_SCALAR_SCORE ) );
|
|
add_cycle_scalar( int( cycle ), cycleScalars, cycle_data );
|
|
}
|
|
}
|
|
|
|
add_cycle_scalar( cycle_index, scalars, cycle_data )
|
|
{
|
|
assert( !isDefined( cycle_data.cycle_scalars[ cycle_index ] ) );
|
|
cycle_data.cycle_scalars[ cycle_index ] = scalars;
|
|
}
|
|
|
|
set_cycle_scalars( cycle_num )
|
|
{
|
|
if ( is_hardcore_mode() )
|
|
{
|
|
level.cycle_health_scalar = level.cycle_data.cycle_scalars[ cycle_num ].health * level.hardcore_health_scalar;
|
|
level.cycle_damage_scalar = level.cycle_data.cycle_scalars[ cycle_num ].damage * level.hardcore_damage_scalar;
|
|
level.cycle_reward_scalar = level.cycle_data.cycle_scalars[ cycle_num ].reward * level.hardcore_reward_scalar;
|
|
level.cycle_score_scalar = level.cycle_data.cycle_scalars[ cycle_num ].score * level.hardcore_score_scalar;
|
|
}
|
|
else if ( is_casual_mode() )
|
|
{
|
|
level.cycle_health_scalar = level.cycle_data.cycle_scalars[ cycle_num ].health * level.casual_health_scalar;
|
|
level.cycle_damage_scalar = level.cycle_data.cycle_scalars[ cycle_num ].damage * level.casual_damage_scalar;
|
|
level.cycle_reward_scalar = level.cycle_data.cycle_scalars[ cycle_num ].reward * level.casual_reward_scalar;
|
|
level.cycle_score_scalar = level.cycle_data.cycle_scalars[ cycle_num ].score * level.casual_score_scalar;
|
|
}
|
|
else
|
|
{
|
|
level.cycle_health_scalar = level.cycle_data.cycle_scalars[ cycle_num ].health;
|
|
level.cycle_damage_scalar = level.cycle_data.cycle_scalars[ cycle_num ].damage;
|
|
level.cycle_reward_scalar = level.cycle_data.cycle_scalars[ cycle_num ].reward;
|
|
level.cycle_score_scalar = level.cycle_data.cycle_scalars[ cycle_num ].score;
|
|
}
|
|
|
|
}
|
|
|
|
init_spawn_node_info( cycle_data )
|
|
{
|
|
cycle_data.spawn_node_info = [];
|
|
|
|
init_spawn_node_info_from_table( cycle_data );
|
|
}
|
|
|
|
init_spawn_node_info_from_table( cycle_data )
|
|
{
|
|
if ( !isDefined ( level.spawn_node_info_table ) )
|
|
level.spawn_node_info_table = SPAWN_NODE_INFO_TABLE;
|
|
|
|
for ( entryIndex = TABLE_SPAWN_NODE_INFO_START_INDEX; entryIndex <= TABLE_SPAWN_NODE_INFO_MAX_INDEX; entryIndex++ )
|
|
{
|
|
key = tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_NODE_KEY );
|
|
if ( key == "" )
|
|
break;
|
|
|
|
node_info = spawnStruct();
|
|
|
|
node_info.validType = tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, TABLE_SPAWN_VALID_ALIEN_TYPE );
|
|
node_info.scriptableStatus = getScriptableStatus( entryIndex );
|
|
|
|
vignetteInfo = [];
|
|
vignetteInfo [ "goon" ] = grabVignetteInfo ( key, entryIndex, TABLE_GOON_VIGNETTE_STATE, TABLE_GOON_VIGNETTE_INDEX_ARRAY, TABLE_GOON_VIGNETTE_LABEL, TABLE_GOON_VIGNETTE_END_NOTETRACK, TABLE_GOON_VIGNETTE_FX, TABLE_GOON_VIGNETTE_SCRIPTABLE, TABLE_GOON_VIGNETTE_SCRIPTABLE_STATE );
|
|
vignetteInfo [ "brute" ] = grabVignetteInfo ( key, entryIndex, TABLE_BRUTE_VIGNETTE_STATE, TABLE_BRUTE_VIGNETTE_INDEX_ARRAY, TABLE_BRUTE_VIGNETTE_LABEL, TABLE_BRUTE_VIGNETTE_END_NOTETRACK, TABLE_BRUTE_VIGNETTE_FX, TABLE_BRUTE_VIGNETTE_SCRIPTABLE, TABLE_BRUTE_VIGNETTE_SCRIPTABLE_STATE );
|
|
vignetteInfo [ "spitter" ] = grabVignetteInfo ( key, entryIndex, TABLE_SPITTER_VIGNETTE_STATE, TABLE_SPITTER_VIGNETTE_INDEX_ARRAY, TABLE_SPITTER_VIGNETTE_LABEL, TABLE_SPITTER_VIGNETTE_END_NOTETRACK, TABLE_SPITTER_VIGNETTE_FX, TABLE_SPITTER_VIGNETTE_SCRIPTABLE, TABLE_SPITTER_VIGNETTE_SCRIPTABLE_STATE );
|
|
vignetteInfo [ "seeder" ] = grabVignetteInfo ( key, entryIndex, TABLE_SPITTER_VIGNETTE_STATE, TABLE_SPITTER_VIGNETTE_INDEX_ARRAY, TABLE_SPITTER_VIGNETTE_LABEL, TABLE_SPITTER_VIGNETTE_END_NOTETRACK, TABLE_SPITTER_VIGNETTE_FX, TABLE_SPITTER_VIGNETTE_SCRIPTABLE, TABLE_SPITTER_VIGNETTE_SCRIPTABLE_STATE );
|
|
vignetteInfo [ "elite" ] = grabVignetteInfo ( key, entryIndex, TABLE_ELITE_VIGNETTE_STATE, TABLE_ELITE_VIGNETTE_INDEX_ARRAY, TABLE_ELITE_VIGNETTE_LABEL, TABLE_ELITE_VIGNETTE_END_NOTETRACK, TABLE_ELITE_VIGNETTE_FX, TABLE_ELITE_VIGNETTE_SCRIPTABLE, TABLE_ELITE_VIGNETTE_SCRIPTABLE_STATE );
|
|
vignetteInfo [ "minion" ] = grabVignetteInfo ( key, entryIndex, TABLE_MINION_VIGNETTE_STATE, TABLE_MINION_VIGNETTE_INDEX_ARRAY, TABLE_MINION_VIGNETTE_LABEL, TABLE_MINION_VIGNETTE_END_NOTETRACK, TABLE_MINION_VIGNETTE_FX, TABLE_MINION_VIGNETTE_SCRIPTABLE, TABLE_MINION_VIGNETTE_SCRIPTABLE_STATE );
|
|
|
|
node_info.vignetteInfo = vignetteInfo;
|
|
|
|
cycle_data.spawn_node_info [ key ] = node_info;
|
|
}
|
|
}
|
|
|
|
grabVignetteInfo ( key, entryIndex, stateIndex, indexArrayIndex, labelIndex, endNotetrackIndex, FX, scriptableTargetname, scriptableState )
|
|
{
|
|
CONST_DELIMITER = ";";
|
|
|
|
state = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, stateIndex ) );
|
|
indexArray = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, indexArrayIndex ) );
|
|
label = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, labelIndex ) );
|
|
endNoteTrack = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, endNotetrackIndex ) );
|
|
fx = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, FX ) );
|
|
scriptableTargetname = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, scriptableTargetname ) );
|
|
scriptableState = replaceEmptyStringWithNone( tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, scriptableState ) );
|
|
|
|
return ( state + CONST_DELIMITER + indexArray + CONST_DELIMITER + label + CONST_DELIMITER + endNoteTrack + CONST_DELIMITER + fx + CONST_DELIMITER + scriptableTargetname + CONST_DELIMITER + scriptableState + CONST_DELIMITER + key );
|
|
}
|
|
|
|
getScriptableStatus( entryIndex )
|
|
{
|
|
string = tablelookup( level.spawn_node_info_table, TABLE_INDEX, entryIndex, TABLE_ONE_OFF_SCRIPTABLE );
|
|
|
|
if ( maps\mp\agents\alien\_alien_agents::is_empty_string( string ) )
|
|
return "always_on";
|
|
else
|
|
return "one_off";
|
|
}
|
|
|
|
replaceEmptyStringWithNone( string )
|
|
{
|
|
if( maps\mp\agents\alien\_alien_agents::is_empty_string( string ) )
|
|
return "NONE";
|
|
|
|
return string;
|
|
}
|
|
|
|
load_cycle_drill_layer_from_table( cycle_data )
|
|
{
|
|
cycle_data.cycle_drill_layers = [];
|
|
cycle_data.cycle_delay_times = [];
|
|
|
|
for ( entryIndex = TABLE_CYCLE_DRILL_LAYER_START_INDEX; entryIndex <= TABLE_CYCLE_DRILL_LAYER_END_INDEX; entryIndex++ )
|
|
{
|
|
cycle = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_DRILL_LAYER_CYCLE );
|
|
if ( cycle == "" )
|
|
break;
|
|
|
|
layers = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_DRILL_LAYERS );
|
|
layers = StrTok( layers, " " );
|
|
|
|
for ( i = 0; i < layers.size; i++ )
|
|
layers[ i ] = int( layers[ i ] );
|
|
|
|
cycle_data.cycle_drill_layers[ int( cycle ) ] = layers;
|
|
|
|
delay_time = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_CYCLE_DRILL_DELAY );
|
|
cycle_data.cycle_delay_times[ int( cycle ) ] = float( delay_time );
|
|
}
|
|
}
|
|
|
|
load_random_hives_from_table()
|
|
{
|
|
removed_hives = [];
|
|
|
|
for ( entryIndex = TABLE_RANDOM_HIVES_START_INDEX; entryIndex <= TABLE_RANDOM_HIVES_END_INDEX; entryIndex++ )
|
|
{
|
|
num_spawned = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_RANDOM_HIVES_NUM_SPAWNED );
|
|
if ( num_spawned == "" )
|
|
break;
|
|
|
|
hives = tablelookup( level.alien_cycle_table, TABLE_INDEX, entryIndex, TABLE_RANDOM_HIVES_HIVE_LIST );
|
|
hives = StrTok( hives, " " );
|
|
|
|
removed_hives = array_combine( removed_hives, get_hives_to_remove( int( num_spawned ), hives ) );
|
|
}
|
|
|
|
level.removed_hives = removed_hives;
|
|
}
|
|
|
|
get_hives_to_remove( num_to_spawn, hive_array )
|
|
{
|
|
num_to_remove = hive_array.size - num_to_spawn;
|
|
assert( num_to_remove >= 0 );
|
|
|
|
if ( num_to_remove == 0 )
|
|
return [];
|
|
|
|
hive_array = array_randomize( hive_array );
|
|
hives_to_remove = [];
|
|
for ( i = 0; i < num_to_remove; i++ )
|
|
{
|
|
hives_to_remove[ hives_to_remove.size ] = hive_array[ i ];
|
|
}
|
|
|
|
return hives_to_remove;
|
|
}
|