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