iw6-scripts-dev/maps/mp/agents/alien/_alien_spitter.gsc
2024-12-11 11:28:08 +01:00

842 lines
23 KiB
Plaintext

#include common_scripts\utility;
#include maps\mp\alien\_utility;
ALIEN_SPIT_ATTACK_DISTANCE_MAX_SQ = 1440000; // 1200 * 1200
ALIEN_ESCAPE_SPIT_ATTACK_DISTANCE_MAX_SQ = 3240000; // 1800 * 1800
MIN_SPIT_TIMES = 3;
MAX_SPIT_TIMES = 6;
SPITTER_NODE_DURATION = 10; // Max length of time they stay at one spit node
SPITTER_FIRE_INTERVAL_MIN = 1.5; // Min amount of time in between a projectile fire
SPITTER_FIRE_INTERVAL_MAX = 3.0; // Max amount of time in between a projectile fire
SPITTER_PROJECTILE_BARRAGE_SIZE_MIN = 2; // Number of small projectiles to shoot at a time when not shooting a gas cloud
SPITTER_PROJECTILE_BARRAGE_SIZE_MAX = 3; // Number of small projectiles to shoot at a time when not shooting a gas cloud
SPITTER_GAS_CLOUD_FIRE_INTERVAL_MIN = 10.0; // Min amount of time in between a gas cloud projectile fire
SPITTER_GAS_CLOUD_FIRE_INTERVAL_MAX = 15.0; // Max amount of time in between a gas cloud projectile fire
SPITTER_GAS_CLOUD_MAX_COUNT = 3; // Max number of active gas clouds in a level
SPITTER_NODE_DAMAGE_DELAY = 0.1; // How long they wait to move from a spit node after getting damaged
SPITTER_MIN_PLAYER_DISTANCE_SQ = 90000.0; // If player gets within 300 units, they'll move to a new spit node
SPITTER_MOVE_MIN_PLAYER_DISTANCE_SQ = 40000.0; // If player gets within 200 units while alien is moving, they'll stop and spit a projectile at them
SPITTER_NODE_INITIAL_FIRE_DELAY_SCALE = 0.5; // Initial scale on delay before a spitter can spit after getting to a node
SPITTER_NO_TARGET_NODE_MOVE_TIME = 1.0; // If no targets at current node for this time, spitter will move
SPITTER_AOE_HEIGHT = 128; // Height of gas cloud
SPITTER_AOE_RADIUS = 150; // Radius of gas cloud
SPITTER_AOE_DURATION = 10.0; // How long the gas cloud lasts
SPITTER_AOE_DELAY = 2.0; // How long after projectile explodes before gas cloud damage is applied
SPITTER_AOE_DAMAGE_PER_SECOND = 12.0; // Damage per second at center of gas cloud
SPITTER_TIME_BETWEEN_SPITS = 3.33; // SPITTER_AOE_DURATION / SPITTER_GAS_CLOUD_MAX_COUNT
SPITTER_LOOK_AHEAD_PERCENTAGE = 0.5; // how accurately the spitters lead the players
SPITTER_ESCAPE_LOOK_AHEAD_PERCENTAGE = 1.0; // how accurately the spitters lead the players during escape sequence
load_spitter_fx()
{
level._effect[ "spit_AOE" ] = LoadFX( "vfx/gameplay/alien/vfx_alien_spitter_gas_cloud" );
level._effect[ "spit_AOE_small" ] = LoadFX( "vfx/gameplay/alien/vfx_alien_spitter_gas_cloud_64" );
}
spitter_init()
{
self.gas_cloud_available = true;
}
spitter_death()
{
release_spit_node();
}
is_escape_sequence_active()
{
return ( flag_exist( "hives_cleared" ) && flag( "hives_cleared" ) );
}
get_max_spit_distance_squared()
{
if ( is_escape_sequence_active() )
return ALIEN_ESCAPE_SPIT_ATTACK_DISTANCE_MAX_SQ;
return ALIEN_SPIT_ATTACK_DISTANCE_MAX_SQ;
}
get_lookahead_percentage()
{
if ( is_escape_sequence_active() )
return SPITTER_ESCAPE_LOOK_AHEAD_PERCENTAGE;
return SPITTER_LOOK_AHEAD_PERCENTAGE;
}
spit_projectile( enemy )
{
if ( self.spit_type == "gas_cloud" )
{
level.spitter_last_cloud_time = gettime();
}
self.melee_type = "spit";
self.spit_target = enemy;
maps\mp\agents\alien\_alien_think::alien_melee( enemy );
}
spit_attack( enemy )
{
/# maps\mp\agents\alien\_alien_think::debug_alien_ai_state( "spit_attack" ); #/
self endon( "melee_pain_interrupt" );
isEnemyChopper = isdefined( enemy ) && isdefined( enemy.code_classname ) && enemy.code_classname == "script_vehicle";
if ( isEnemyChopper )
targetedEnemy = enemy;
else
targetedEnemy = self.spit_target;
targetedEnemy endon( "death" );
self maps\mp\agents\alien\_alien_anim_utils::turnTowardsEntity( targetedEnemy );
if ( IsAlive( targetedEnemy ) )
{
self.spit_target = targetedEnemy;
if ( isEnemyChopper )
{
aim_ahead_factor = 5; // factor of speed MPH
aim_ahead_unit_vec = VectorNormalize( AnglesToForward( targetedEnemy.angles ) ); // direction
aim_ahead_speed_mag = Length( targetedEnemy Vehicle_GetVelocity() ) * aim_ahead_factor; // scaler
aim_ahead_vec = aim_ahead_unit_vec * aim_ahead_speed_mag; // aim ahead offset vector
self.spit_target_location = targetedEnemy.origin + aim_ahead_vec + ( 0, 0, 32 ); // offset by 32 down from origin as origin is at rotor
}
else
{
self.spit_target_location = targetedEnemy.origin;
}
self.looktarget = targetedEnemy;
self set_alien_emissive( 0.2, 1.0 );
if ( IsDefined ( self.current_spit_node ) && !maps\mp\alien\_utility::is_normal_upright( AnglesToUp( self.current_spit_node.angles ) ) )
{
up = AnglesToUp( self.current_spit_node.angles );
forward = AnglesToForward( self.angles );
left = VectorCross( up, forward );
forward = VectorCross( left, up );
right = (0,0,0) - left;
anglesToFace = AxisToAngles( forward, right, up );
self ScrAgentSetOrientMode( "face angle abs", anglesToFace );
}
else if ( IsDefined( self.enemy ) && targetedEnemy == self.enemy )
{
self ScrAgentSetOrientMode( "face enemy" );
}
else
{
forward = VectorNormalize( targetedEnemy.origin - self.origin );
if ( IsDefined( self.current_spit_node ) )
up = AnglesToUp( self.current_spit_node.angles );
else
up = AnglesToUp( self.angles );
left = VectorCross( up, forward );
forward = VectorCross( left, up );
right = (0,0,0) - left;
anglesToFace = AxisToAngles( forward, right, up );
self ScrAgentSetOrientMode( "face angle abs", anglesToFace );
}
if( self.oriented )
self ScrAgentSetAnimMode( "anim angle delta" );
else
self ScrAgentSetAnimMode( "anim deltas" );
play_spit_anim();
}
self set_alien_emissive_default( 0.2 );
self.looktarget = undefined;
self.spit_target = undefined;
self.spit_target_location = undefined;
self.spit_type = undefined;
}
play_spit_anim()
{
switch ( self.spit_type )
{
case "close_range":
self maps\mp\agents\_scriptedagents::PlayAnimUntilNotetrack( "close_spit_attack", "spit_attack", "end", ::handleAttackNotetracks );
break;
case "gas_cloud":
self maps\mp\agents\_scriptedagents::PlayAnimUntilNotetrack( "gas_spit_attack", "spit_attack", "end", ::handleAttackNotetracks );
break;
case "long_range":
barrage_count = RandomIntRange( SPITTER_PROJECTILE_BARRAGE_SIZE_MIN, SPITTER_PROJECTILE_BARRAGE_SIZE_MAX );
for ( spitIndex = 0; spitIndex < barrage_count; spitIndex++ )
self maps\mp\agents\_scriptedagents::PlayAnimUntilNotetrack( "long_range_spit_attack", "spit_attack", "end", ::handleAttackNotetracks );
break;
default:
AssertMsg( self.spit_type + " is an invalid spit type!" );
break;
}
}
get_best_spit_target( targeted_enemy )
{
if ( cointoss() && get_alien_type() != "seeder" )
{
griefTargets = get_grief_targets();
foreach ( griefTarget in griefTargets )
{
if ( is_valid_spit_target( griefTarget, false ) )
return griefTarget;
}
wait 0.05;
}
if ( IsDefined( targeted_enemy ) )
{
if ( IsAlive( targeted_enemy ) && is_valid_spit_target( targeted_enemy, false ) )
return targeted_enemy;
}
possibleTargets = self get_current_possible_targets();
MAX_TARGET_TESTS_PER_FRAME = 4;
currentTestsThisFrame = 0;
foreach ( possibleTarget in possibleTargets )
{
if ( !IsAlive( possibleTarget ) )
continue;
if ( IsDefined( targeted_enemy ) && possibleTarget == targeted_enemy )
continue;
if ( is_valid_spit_target( possibleTarget, true ) )
return possibleTarget;
currentTestsThisFrame++;
if ( currentTestsThisFrame >= MAX_TARGET_TESTS_PER_FRAME )
{
waitframe();
currentTestsThisFrame = 0;
}
}
return undefined;
}
get_grief_targets()
{
griefTargets = [];
if ( !can_spit_gas_cloud( ) || is_pet())
return griefTargets;
foreach( player in level.players )
{
if ( !IsAlive( player ) )
continue;
if ( IsDefined( player.inLastStand ) && player.inLastStand )
griefTargets[griefTargets.size] = player;
}
if ( IsDefined( level.drill ) && IsDefined( level.drill.state ) && level.drill.state == "offline" )
griefTargets[griefTargets.size] = level.drill;
return array_randomize( griefTargets );
}
is_valid_spit_target( spit_target, check_attacker_values )
{
if ( !isAlive( spit_target ) )
{
return false;
}
if ( check_attacker_values && IsPlayer( spit_target ) && !has_attacker_space( spit_target ) )
{
return false;
}
maxValidDistanceSq = get_max_spit_distance_squared();
flatDistanceToTargetSquared = Distance2DSquared( self.origin, spit_target.origin );
if ( flatDistanceToTargetSquared > maxValidDistanceSq )
return false;
self.looktarget = spit_target;
if ( !isAlive( spit_target ) )
{
return false;
}
if (( isPlayer( spit_target ) || IsSentient( spit_target )) && !IsDefined( spit_target.usingRemote ) )
endPos = spit_target getEye();
else
endPos = spit_target.origin;
spitFirePos = self GetTagOrigin( "TAG_BREATH" );
return BulletTracePassed( spitFirePos, endPos, false, self );
}
get_spit_fire_pos( spit_target )
{
return self GetTagOrigin( "TAG_BREATH" );
}
has_attacker_space( player )
{
maxValidAttackerValue = level.maxAlienAttackerDifficultyValue - level.alien_types[ self.alien_type ].attributes[ "attacker_difficulty" ];
targetedAttackerScore = maps\mp\agents\alien\_alien_think::get_current_attacker_value( player );
return ( targetedAttackerScore <= maxValidAttackerValue );
}
handleAttackNotetracks( note, animState, animIndex, animTime )
{
if( isDefined( level.dlc_attacknotetrack_override_func ))
{
self [[level.dlc_attacknotetrack_override_func]]( note, animState, animIndex, animTime );
return;
}
if ( note == "spit" )
return self fire_spit_projectile();
}
fire_spit_projectile()
{
if ( !IsDefined( self.spit_target ) && !IsDefined( self.spit_target_location ) )
return;
hasValidTarget = IsAlive( self.spit_target );
isTargetChopper = isdefined( self.spit_target.code_classname ) && self.spit_target.code_classname == "script_vehicle";
if ( hasValidTarget && !isTargetChopper )
targetLocation = self.spit_target.origin;
else
targetLocation = self.spit_target_location;
if ( self.spit_type == "gas_cloud" )
{
spit_gas_cloud_projectile( targetLocation );
}
else if ( hasValidTarget )
{
PROJECTILE_SPEED = 1400;
targetLocation = get_lookahead_target_location( PROJECTILE_SPEED, self.spit_target, false );
if ( !BulletTracePassed( targetLocation, get_spit_fire_pos( targetLocation ), false, self ) )
targetLocation = get_lookahead_target_location( PROJECTILE_SPEED, self.spit_target, true );
spit_basic_projectile( targetLocation );
}
}
get_lookahead_target_location( projectile_speed, target, use_eye_location )
{
if ( !IsPlayer( target ) )
return target.origin;
lookAheadPercentage = get_lookahead_percentage();
if ( use_eye_location && !IsDefined( target.usingRemote ))
targetLocation = target GetEye();
else
targetLocation = target.origin;
distanceToTarget = Distance( self.origin, targetLocation);
timeToImpact = distanceToTarget / projectile_speed;
targetVelocity = target GetVelocity();
return targetLocation + targetVelocity * lookAheadPercentage * timeToImpact;
}
can_spit_gas_cloud()
{
if ( !self.gas_cloud_available )
return false;
if ( isdefined( self.enemy ) && isdefined( self.enemy.no_gas_cloud_attack ) && self.enemy.no_gas_cloud_attack )
return false;
time_since_last_spit = (gettime() - level.spitter_last_cloud_time) * 0.001;
return level.spitter_gas_cloud_count < SPITTER_GAS_CLOUD_MAX_COUNT && time_since_last_spit > SPITTER_TIME_BETWEEN_SPITS;
}
spit_basic_projectile( targetLocation )
{
spitFirePos = get_spit_fire_pos( targetLocation );
spitProjectile = MagicBullet( "alienspit_mp", spitFirePos, targetLocation, self );
spitProjectile.owner = self;
if ( IsDefined( spitProjectile ) )
spitProjectile thread spit_basic_projectile_impact_monitor( self );
}
spit_basic_projectile_impact_monitor( owner )
{
self waittill( "explode", explodeLocation );
if ( !IsDefined( explodeLocation ) )
return;
PlayFx( level._effect[ "spit_AOE_small" ], explodeLocation + (0,0,8), (0,0,1), (1,0,0) );
}
spit_gas_cloud_projectile( targetLocation )
{
spitFirePos = get_spit_fire_pos( targetLocation );
spitProjectile = MagicBullet( "alienspit_gas_mp", spitFirePos, targetLocation, self );
spitProjectile.owner = self;
if ( IsDefined( spitProjectile ) )
spitProjectile thread spit_gas_cloud_projectile_impact_monitor( self );
self thread gas_cloud_available_timer();
}
gas_cloud_available_timer()
{
self endon( "death" );
self.gas_cloud_available = false;
cloudInterval = RandomFloatRange( SPITTER_GAS_CLOUD_FIRE_INTERVAL_MIN, SPITTER_GAS_CLOUD_FIRE_INTERVAL_MAX );
wait cloudInterval;
self.gas_cloud_available = true;
}
spit_gas_cloud_projectile_impact_monitor( owner )
{
self waittill( "explode", explodeLocation );
if ( !IsDefined( explodeLocation ) )
return;
trigger = Spawn( "trigger_radius", explodeLocation, 0, SPITTER_AOE_RADIUS, SPITTER_AOE_HEIGHT );
// sanity check. Need to come up with more robust fallback
if ( !IsDefined( trigger ) )
return;
level.spitter_gas_cloud_count++;
trigger.onPlayer = true;
PlayFx( level._effect[ "spit_AOE" ], explodeLocation + (0,0,8),(0,0,1), (1,0,0) );
thread spit_aoe_cloud_damage( explodeLocation, trigger );
level notify( "spitter_spit",explodeLocation );
wait SPITTER_AOE_DURATION;
trigger Delete();
level.spitter_gas_cloud_count--;
}
spit_aoe_cloud_damage( impact_location, trigger )
{
trigger endon( "death" );
wait SPITTER_AOE_DELAY;
while ( true )
{
trigger waittill( "trigger", player );
if ( !IsPlayer( player ) )
continue;
if ( !IsAlive( player ) )
continue;
disorient_player( player );
damage_player( player, trigger );
}
}
damage_player( player, trigger )
{
DAMAGE_INTERVAL = 0.5;
currentTime = GetTime();
if ( !IsDefined( player.last_spitter_gas_damage_time ) )
{
elapsedTime = DAMAGE_INTERVAL;
}
else if (player.last_spitter_gas_damage_time + DAMAGE_INTERVAL * 1000.0 > currentTime )
{
return;
}
else
{
elapsedTime = Min( DAMAGE_INTERVAL, (currentTime - player.last_spitter_gas_damage_time) * 0.001 );
}
gas_damage_scalar = player maps\mp\alien\_perk_utility::perk_GetGasDamageScalar();
damageAmount = int(SPITTER_AOE_DAMAGE_PER_SECOND * elapsedTime * gas_damage_scalar );
if ( damageAmount > 0 )
{
player thread [[ level.callbackPlayerDamage ]]( trigger, trigger, damageAmount, 0, "MOD_SUICIDE", "alienspit_gas_mp", trigger.origin, ( 0,0,0 ), "none", 0 );
}
player.last_spitter_gas_damage_time = currentTime;
}
disorient_player( player )
{
if ( is_chaos_mode() && player maps\mp\alien\_perk_utility::perk_GetGasDamageScalar() == 0 )
return;
else if( !player maps\mp\alien\_perk_utility::has_perk( "perk_medic", [ 1,2,3,4 ] ) )
{
if ( isDefined( level.shell_shock_override ))
player [[level.shell_shock_override]]( 0.5 );
else
player ShellShock( "alien_spitter_gas_cloud", 0.5 );
}
}
get_RL_toward( target )
{
//Return the right/left vector toward target
self_to_target_angles = VectorToAngles( target.origin - self.origin );
target_direction = anglesToRight( self_to_target_angles );
if ( common_scripts\utility::cointoss())
target_direction *= -1;
return target_direction;
}
spitter_combat( enemy )
{
self endon( "bad_path" );
self endon( "death" );
self endon ( "alien_main_loop_restart" );
while ( true )
{
attackNode = find_spitter_attack_node( self.enemy );
if ( IsDefined( attackNode ) )
{
move_to_spitter_attack_node( attackNode );
spitter_attack( self.enemy );
}
else
{
wait 0.05;
}
}
}
release_spit_node()
{
if ( IsDefined( self.current_spit_node ) )
{
self ScrAgentRelinquishClaimedNode( self.current_spit_node );
self.current_spit_node.claimed = false;
self.current_spit_node = undefined;
}
}
claim_spit_node( spit_node )
{
self.current_spit_node = spit_node;
spit_node.claimed = true;
self ScrAgentClaimNode( spit_node );
}
move_to_spitter_attack_node( attack_node )
{
self endon( "player_proximity_during_move" );
release_spit_node();
claim_spit_node( attack_node );
self ScrAgentSetGoalNode( attack_node );
self ScrAgentSetGoalRadius( 64 );
self thread enemy_proximity_during_move_monitor();
self waittill( "goal_reached" );
}
enemy_proximity_during_move_monitor()
{
self endon( "death" );
self endon( "goal_reached" );
self endon ( "alien_main_loop_restart" );
while ( true )
{
wait 0.05;
if ( !is_normal_upright( AnglesToUp( self.angles ) ) )
continue;
if ( !self maps\mp\agents\alien\_alien_think::melee_okay() )
continue;
if ( IsDefined( self.valid_moving_spit_attack_time ) && GetTime() < self.valid_moving_spit_attack_time )
continue;
closePlayer = find_player_within_distance( SPITTER_MOVE_MIN_PLAYER_DISTANCE_SQ );
if ( IsDefined( closePlayer ) )
break;
}
release_spit_node();
self notify( "player_proximity_during_move" );
self ScrAgentSetGoalEntity( closePlayer );
self ScrAgentSetGoalRadius( 2048.0 );
self waittill( "goal_reached" );
}
get_possible_spitter_attack_nodes( target_entity )
{
if( get_alien_type() == "seeder" )
attackNodes = GetNodesInRadius( target_entity.origin, 768, 128, 512, "jump attack" );
else
attackNodes = GetNodesInRadius( target_entity.origin, 1000, 300, 512, "jump attack" );
validNodes = [];
foreach( attackNode in attackNodes )
{
if ( IsDefined( attackNode.claimed) && attackNode.claimed )
continue;
validNodes[validNodes.size] = attackNode;
}
return validNodes;
}
is_pet()
{
return ( IsDefined( self.pet ) && self.pet );
}
get_current_possible_targets()
{
if ( is_pet() )
return level.agentArray;
else
return level.players;
}
find_spitter_attack_node( target_enemy )
{
nearbySpitNodes = [];
if ( is_escape_sequence_active() && IsDefined( level.escape_spitter_target_node ) )
{
nearbySpitNodes = get_possible_spitter_attack_nodes( level.escape_spitter_target_node );
if ( nearbySpitNodes.size > 0 )
target_enemy = level.escape_spitter_target_node;
}
if ( nearbySpitNodes.size == 0 && IsDefined( target_enemy ) )
nearbySpitNodes = get_possible_spitter_attack_nodes( target_enemy );
if ( nearbySpitNodes.size == 0 )
{
possibleTargets = self get_current_possible_targets();
foreach ( possibleTarget in possibleTargets )
{
wait 0.05;
if ( !IsAlive( possibleTarget ) )
continue;
if ( IsDefined( target_enemy ) && possibleTarget == target_enemy )
continue;
nearbySpitNodes = get_possible_spitter_attack_nodes( possibleTarget );
if ( nearbySpitNodes.size > 0 )
{
target_enemy = possibleTarget;
break;
}
}
}
if ( nearbySpitNodes.size == 0 )
nearbySpitNodes = get_possible_spitter_attack_nodes( self );
if ( nearbySpitNodes.size == 0 )
return undefined;
filters = [];
if ( IsDefined( target_enemy ) )
{
filters[ "dist_from_enemy_weight" ] = 8.0;
filters[ "enemy_los_weight" ] = 6.0;
filters[ "height_weight" ] = 4.0;
target_direction = get_RL_toward( target_enemy );
target_enemy endon( "death" );
}
else
{
filters[ "dist_from_enemy_weight" ] = 0.0;
filters[ "enemy_los_weight" ] = 0.0;
filters[ "height_weight" ] = 0.0;
target_direction = get_central_enemies_direction();
}
filters[ "direction" ] = "override";
filters[ "direction_override" ] = target_direction;
filters[ "direction_weight" ] = 1.0;
filters[ "min_height" ] = 64.0;
filters[ "max_height" ] = 400.0;
filters[ "enemy_los" ] = true;
filters[ "min_dist_from_enemy" ] = 300.0;
filters[ "max_dist_from_enemy" ] = 800.0;
filters[ "desired_dist_from_enemy" ] = 600.0;
filters[ "min_dist_from_all_enemies" ] = 300.0;
filters[ "min_dist_from_all_enemies_weight" ] = 5.0;
filters[ "not_recently_used_weight" ] = 10.0;
filters[ "recently_used_time_limit" ] = 30.0;
filters[ "random_weight" ] = 1.0;
result = maps\mp\agents\alien\_alien_think::get_retreat_node_rated( target_enemy, filters, nearbySpitNodes );
return result;
}
get_central_enemies_direction()
{
possibleTargets = self get_current_possible_targets();
if ( possibleTargets.size == 0)
return self.origin + AnglesToForward( self.angles ) * 100;
centralLocation = ( 0, 0, 0 );
foreach ( possibleTarget in possibleTargets )
centralLocation += possibleTarget.origin;
centralLocation = centralLocation / possibleTargets.size;
return centralLocation - self.origin;
}
spitter_attack( enemy )
{
self endon( "spitter_node_move_requested" );
if ( !IsDefined( self.current_spit_node ) )
{
choose_spit_type( "close_range" );
spit_projectile( enemy );
self.valid_moving_spit_attack_time = GetTime() + RandomFloatRange( SPITTER_FIRE_INTERVAL_MIN, SPITTER_FIRE_INTERVAL_MAX ) * 1000.0;
return;
}
set_up_attack_node_watchers();
if ( !is_escape_sequence_active() )
wait RandomFloatRange( SPITTER_FIRE_INTERVAL_MIN, SPITTER_FIRE_INTERVAL_MAX ) * SPITTER_NODE_INITIAL_FIRE_DELAY_SCALE;
while ( true )
{
targetedEnemy = undefined;
no_target_time = 0.0;
while ( !IsDefined( targetedEnemy ) )
{
no_target_time += 0.2;
if ( no_target_time >= SPITTER_NO_TARGET_NODE_MOVE_TIME )
{
return;
}
wait 0.2;
if ( IsDefined( enemy ) && IsDefined( enemy.code_classname ) && enemy.code_classname == "script_vehicle" )
{
targetedEnemy = enemy;
}
else
{
targetedEnemy = get_best_spit_target( enemy );
}
}
choose_spit_type( "long_range" );
spit_projectile( targetedEnemy );
wait RandomFloatRange( SPITTER_FIRE_INTERVAL_MIN, SPITTER_FIRE_INTERVAL_MAX );
}
}
choose_spit_type( default_type )
{
if ( !is_pet() && can_spit_gas_cloud() )
self.spit_type = "gas_cloud";
else
self.spit_type = default_type;
}
set_up_attack_node_watchers()
{
self thread spitter_node_duration_monitor( SPITTER_NODE_DURATION );
self thread spitter_node_attacked_monitor( SPITTER_NODE_DAMAGE_DELAY );
if ( !is_pet() )
self thread spitter_node_player_proximity( SPITTER_MIN_PLAYER_DISTANCE_SQ );
}
spitter_node_duration_monitor( duration )
{
self endon( "spitter_node_move_requested" );
self endon( "death" );
self endon ( "alien_main_loop_restart" );
wait duration;
self notify( "spitter_node_move_requested" );
}
spitter_node_attacked_monitor( damage_delay )
{
self endon( "spitter_node_move_requested" );
self endon( "death" );
self endon ( "alien_main_loop_restart" );
self waittill( "damage" );
wait damage_delay;
self notify( "spitter_node_move_requested" );
}
spitter_node_player_proximity( min_player_distances_sq )
{
self endon( "spitter_node_move_requested" );
self endon( "death" );
self endon ( "alien_main_loop_restart" );
while ( true )
{
closePlayer = find_player_within_distance( min_player_distances_sq );
if ( IsDefined( closePlayer ) )
break;
wait 0.2;
}
self notify( "spitter_node_move_requested" );
}
find_player_within_distance( distance_sq )
{
foreach( player in level.players )
{
if ( !IsAlive( player ) )
continue;
flatDistanceToPlayerSq = Distance2DSquared( self.origin, player.origin );
if ( flatDistanceToPlayerSq < distance_sq )
return player;
}
return undefined;
}