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

1202 lines
32 KiB
Plaintext

#include maps\mp\agents\_scriptedAgents;
#include maps\mp\agents\alien\_alien_anim_utils;
#include maps\mp\alien\_utility;
#include common_scripts\utility;
SWIPE_ATTACK_START_SOUND = "alien_attack";
LEAP_ATTACK_START_SOUND = "alien_jump";
WALL_ATTACK_START_SOUND = "alien_attack";
ALIEN_MELEE_POSTURE_PERCENT = 20;
ALIEN_MELEE_COMBAT_STRAFE_PERCENT = 100;
ALIEN_MELEE_COMBAT_STRAFE_MIN_DIST_SQR = 20000.0; // 141.0 * 141.0
MP_ALIEN_DLC3_DRILL_MELEE_SCALAR = 2;
ALIEN_MELEE_MOVE_SIDE_PERCENT = 60;
MAX_SWIPE_DAMAGE_DIST = 90;
MAX_LONG_RANGE_DAMAGE_DIST = 200;
SWIPE_OFFSET_XY = 56; // how far in front of player we want to swipe (so the player can actually see it.)
LEAP_OFFSET_XY = 48;
main()
{
self endon( "killanimscript" );
assert( IsDefined( self.enemy ) );
assert( IsDefined( self.melee_type ) );
self.enemy thread melee_clean_up( self );
self ScrAgentSetOrientMode( "face enemy" );
self.playing_pain_animation = false;
self.melee_jumping = false;
self.melee_jumping_to_wall = false;
self.melee_success = false;
self.melee_synch = false;
startTime = gettime();
self.lastAttackTime = startTime;
switch( self.melee_type )
{
case "swipe":
self melee_swipe( self.enemy );
break;
case "leap":
self melee_leap( self.enemy );
break;
case "wall":
self melee_wall( self.enemy );
break;
case "synch":
self melee_synch_attack( self.enemy );
break;
case "swipe_static":
self melee_swipe_static( self.enemy );
break;
case "badpath_jump":
self badpath_jump( self.enemy );
break;
case "spit":
self maps\mp\agents\alien\_alien_spitter::spit_attack( self.enemy );
break;
case "charge":
self maps\mp\agents\alien\_alien_elite::do_charge_attack( self.enemy );
break;
case "slam":
self maps\mp\agents\alien\_alien_elite::do_ground_slam( self.enemy );
break;
case "angered":
//self maps\mp\agents\alien\_alien_elite::activate_health_regen();
self maps\mp\agents\alien\_alien_elite::activate_angered_state();
break;
case "explode":
self maps\mp\agents\alien\_alien_minion::explode( self.enemy );
break;
default:
// Check for level specific overrides
if( isDefined( level.dlc_melee_override_func ))
{
self [[level.dlc_melee_override_func]]( self.enemy );
}
else
assertmsg( self.melee_type + " unimplemented or you didnt setup a level specific dlc_melee_override_func" );
break;
}
if ( self.playing_pain_animation )
{
self waittill( "pain_finished" );
}
if ( starttime == gettime() )
{
wait 0.05; // Ugh. We have to wait at least one frame to let our calling script start its waittill
}
self notify( "melee_complete" );
}
end_script()
{
self.allowpain = true;
self ScrAgentSetAnimScale( 1, 1 );
self.previousAnimState = "melee";
self.melee_in_move_back = false;
self set_alien_emissive_default( 0.2 );
}
melee_swipe( enemy )
{
self endon( "melee_pain_interrupt" );
// We will either use anims only from swipe, from bite, or we'll choose randomly each time
attack_type = Random( [ "swipe", "bite", "random" ] );
//attack_melee_swipe
successfulSwipeCount = try_preliminary_swipes( attack_type, enemy, SWIPE_OFFSET_XY, MAX_SWIPE_DAMAGE_DIST );
// If we missed with either
if ( successfulSwipeCount != 2 )
{
result3 = do_single_swipe( attack_type, 2, enemy, 0.8, SWIPE_OFFSET_XY, MAX_SWIPE_DAMAGE_DIST );
if ( !result3 && successfulSwipeCount == 0 )
{
try_leap_melee( enemy );
}
}
self move_back( enemy );
}
MAX_LEAP_MELEE_DROP_DISTANCE = 64;
try_leap_melee( enemy )
{
MIN_LEAP_DISTANCE_SQ = 10000;
if ( !IsDefined( enemy ) )
return;
self.leapEndPos = get_leap_end_pos( 1.0, LEAP_OFFSET_XY, enemy );
self.leapEndPos = DropPosToGround( self.leapEndPos, MAX_LEAP_MELEE_DROP_DISTANCE );
// If we found no valid ground under the end node, don't jump
if ( !IsDefined( self.leapEndPos ) )
{
return;
}
// If we're less than the minimum leap distance, don't jump
if ( DistanceSquared( self.origin, self.leapEndPos ) < MIN_LEAP_DISTANCE_SQ )
{
return;
}
// If we can't jump between these points without hitting geo, don't jump
if ( !TrajectoryCanAttemptAccurateJump( self.origin, AnglesToUp( self.angles ), self.leapEndPos, AnglesToUp( enemy.angles ), level.alien_jump_melee_gravity, 1.01 * level.alien_jump_melee_speed ) )
{
return;
}
// If there are no nodes near the end pos, don't jump
nodes = GetNodesInRadiusSorted( self.leapEndPos, 256, 0, 256 );
if ( !nodes.size )
{
return;
}
// If we can't navigate back to the enemy from the jump end position, don't jump
pathdist = GetPathDist( self.leapEndPos, nodes[0].origin, 512 );
if ( pathDist < 0 )
{
return;
}
self melee_leap( enemy );
}
try_preliminary_swipes( attack_type, enemy, swipe_offset, max_damage_distance )
{
successfulSwipeCount = 0;
if ( do_single_swipe( attack_type, 0, enemy, 1.0, swipe_offset, max_damage_distance ) )
successfulSwipeCount = 1;
if ( cointoss() )
{
result2 = do_single_swipe( attack_type, 1, enemy, 1.0, swipe_offset, max_damage_distance );
}
else
{
result2 = false;
}
if ( result2 )
successfulSwipeCount++;
return successfulSwipeCount;
}
do_single_swipe( meleeType, animEntry, enemy, tracking_amount, swipe_offset, max_damage_dist )
{
animState = get_anim_state_from_melee_type( meleeType );
result = false;
if ( !isAlive( enemy ) )
{
return false;
}
self set_alien_emissive( 0.2, 1.0 );
self PlaySoundOnMovingEnt( SWIPE_ATTACK_START_SOUND );
attackAnim = self GetAnimEntry( animState, animEntry );
animLength = GetAnimLength( attackAnim );
target_pos = self get_melee_position( animLength, swipe_offset, enemy, tracking_amount );
if ( isDefined( target_pos ) && is_valid_swipe_position( target_pos ) )
{
attackAnim = self GetAnimEntry( animState, animEntry );
attackTranslation = GetMoveDelta( attackAnim );
animXY = Length2D( attackTranslation );
meToTarget = target_pos - self.origin;
meToEnemy = enemy.origin - self.origin;
if ( ( animXY == 0 ) || VectorDot( meToTarget, meToEnemy ) < 0 )
{
animXYScale = 0;
}
else
{
meToTargetDistXY = Max( 0, Length2D( meToTarget ) );
animXYScale = meToTargetDistXY / animXY;
}
result = perform_swipe( animState, animEntry, enemy, max_damage_dist, animXYScale );
}
else
{
result = perform_swipe( "attack_melee_swipe", animEntry, enemy, MAX_LONG_RANGE_DAMAGE_DIST, 0.0 );
}
return result;
}
perform_swipe( animState, animEntry, enemy, max_damage_dist, anim_XY_scale )
{
result = false;
self ScrAgentSetPhysicsMode( "gravity" );
self ScrAgentSetAnimScale( anim_XY_scale, 0.0 );
self ScrAgentSetAnimMode( "anim deltas" );
self ScrAgentSetOrientMode( "face enemy" );
animRate = RandomFloatRange( 0.9, 1.0 );
self PlayAnimNAtRateUntilNotetrack( animState, animEntry, animRate, "attack_melee", "start_melee" );
self set_alien_emissive_default( 0.2 );
if ( isAlive( enemy ) )
{
dist_sqr = DistanceSquared( self.origin, enemy.origin );
if ( DistanceSquared( self.origin, enemy.origin ) < max_damage_dist * max_damage_dist )
{
self melee_DoDamage( enemy, "swipe" );
result = true;
}
}
self WaitUntilNotetrack( "attack_melee", "end" );
return result;
}
get_anim_state_from_melee_type( meleeType )
{
animStates = [ "attack_melee_swipe", "attack_melee_swipe", "attack_melee_bite", "attack_melee_bite_2" ];
switch ( meleeType )
{
case "random":
return Random( animStates );
case "swipe":
return "attack_melee_swipe";
case "bite":
return Random( [ "attack_melee_bite", "attack_melee_bite_2" ] );
default:
AssertMsg( "Invalid meleeType specified" );
break;
}
return undefined;
}
is_valid_swipe_position( swipe_pos )
{
MAX_SWIPE_ANIM_DISTANCE_SQR = 90000.0; // 300.0 * 300.0
ALIEN_SWIPE_STEP_SIZE = 17.0;
if ( DistanceSquared( self.origin, swipe_pos ) > MAX_SWIPE_ANIM_DISTANCE_SQR )
{
return false;
}
// Make sure we can get to our enemy
return CanMovePointToPoint( self.origin, swipe_pos, ALIEN_SWIPE_STEP_SIZE );
}
get_leap_end_pos( timeUntilAttack, offset, enemy, tracking_amount )
{
enemy_to_self = self.origin - enemy.origin;
enemy_to_self *= ( 1, 1, 0 );
enemy_to_self = VectorNormalize( enemy_to_self ) * offset;
if ( !isDefined( tracking_amount ) )
{
tracking_amount = 1.0;
}
// Calc enemy origin with velocity
if ( isPlayer( enemy ) )
{
velocity = enemy GetVelocity();
TRACKING_VELOCITY_CAP = 200.0;
if ( LengthSquared( velocity ) > TRACKING_VELOCITY_CAP * TRACKING_VELOCITY_CAP )
{
velocity = VectorNormalize( velocity );
velocity *= TRACKING_VELOCITY_CAP;
}
velocity *= tracking_amount; // Don't follow player perfectly
velocity *= timeUntilAttack;
}
else
{
velocity = (0,0,0);
}
/*
// Not far enough!
if ( LengthSquared( enemy_to_self + velocity ) < meleeOffsetXY * meleeOffsetXY )
{
return undefined;
}*/
return enemy.origin + enemy_to_self + velocity;
}
get_melee_position( timeUntilAttack, offset, enemy, tracking_amount )
{
assert( IsAlive( enemy ) );
leapEndPos = get_leap_end_pos( timeUntilAttack, offset, enemy, tracking_amount );
if ( IsDefined( self.bad_path_handled ) && self.bad_path_handled )
{
minDropDistance = 40.0;
self.bad_path_handled = false;
}
else
{
minDropDistance = 18.0;
}
MAX_SLOPE = 0.57735; // tan 30
MAX_DROP_DISTANCE = 56.0;
dropDistance = clamp( MAX_SLOPE * Distance2D( self.origin, leapEndPos ), minDropDistance, MAX_DROP_DISTANCE );
leapEndPos = DropPosToGround( leapEndPos, dropDistance );
/#
if ( !isDefined( leapEndPos ) )
{
println( "get_melee_position: Failed to DropPosToGround: " + enemy.origin );
}
else
{
if ( GetDvarInt( "alien_debug_melee_position" ) == 1 )
{
line ( enemy.origin + ( 0,0,32 ), enemy.origin, (1,0,0), 1, 0, 100 );
line ( self.origin + ( 0,0,32 ), self.origin, (0,1,0), 1, 0, 100 );
line ( leapEndPos + ( 0,0,32 ), leapEndPos, (0,0,1), 1, 0, 100 );
println( "Dist from leapEndPos to enemy: " + Distance( leapEndPos, enemy.origin ) );
}
}
#/
return leapEndPos;
}
melee_leap( enemy )
{
melee_leap_internal( enemy, "leap" );
}
melee_leap_internal( enemy, melee_type )
{
play_leap_start_sound( melee_type );
jumpCBs = SpawnStruct();
jumpCBs.fnSetAnimStates = ::melee_SetJumpAnimStates;
jumpCBs.fnLandAnimStateChoice = ::melee_ChooseJumpArrival;
// leapEndPos should have been calculated for us
assert( IsDefined( self.leapEndPos ) );
leapEndPos = self.leapEndPos;
if ( IsDefined( leapEndPos ) )
{
shouldExplode = maps\mp\alien\_utility::should_explode();
if ( !shouldExplode )
self thread melee_LeapWaitForDamage( enemy, melee_type );
self.melee_jumping = true;
self set_alien_emissive( 0.2, 1.0 );
self maps\mp\agents\alien\_alien_jump::Jump( self.origin, self.angles, leapEndPos, enemy.angles, undefined, jumpCBs );
self set_alien_emissive_default( 0.2 );
self.melee_jumping = false;
if ( shouldExplode )
self maps\mp\agents\alien\_alien_minion::explode( self.enemy );
else
self move_back( enemy );
}
else
{
// Failed to find good position
/#
println( "Leap Melee failed" );
#/
wait 0.05; // We must guarantee that melee takes at least one frame
}
}
play_leap_start_sound( melee_type )
{
switch ( melee_type )
{
case "leap":
self PlaySoundOnMovingEnt( LEAP_ATTACK_START_SOUND );
break;
case "wall":
self PlaySoundOnMovingEnt( WALL_ATTACK_START_SOUND );
break;
default:
break;
}
}
melee_LeapWaitForDamage( enemy, melee_type )
{
self endon( "killanimscript" );
self endon( "melee_pain_interrupt" );
self endon( "jump_pain_interrupt" );
cMeleeDistanceSq = 80 * 80;
while ( true )
{
if ( !IsDefined( enemy ) || !IsAlive( enemy ) )
break;
if ( Distance2DSquared( self.origin, enemy.origin ) <= cMeleeDistanceSq )
{
self melee_DoDamage( enemy, melee_type );
break;
}
wait( 0.1 );
}
}
melee_SetJumpAnimStates( jumpInfo, jumpAnimStates )
{
jumpAnimStates.landAnimState = "attack_leap_swipe";
jumpAnimStates.landAnimEntry = GetRandomAnimEntry( "attack_leap_swipe" );
}
melee_ChooseJumpArrival( jumpInfo, jumpAnimStates )
{
COS_45 = 0.707;
if ( isAlive( self.enemy ) )
{
assert( isDefined( jumpInfo.landOrigin ) );
assert( isDefined( jumpInfo.endAngles ) );
landToEnemy = VectorNormalize( self.enemy.origin - jumpInfo.landOrigin );
landForward = AnglesToForward( jumpInfo.endAngles );
forward_dot = VectorDot( landToEnemy, landForward );
if ( forward_dot > COS_45 )
{
// Already set to forward attack
return;
}
landRight = AnglesToRight( jumpInfo.endAngles );
right_dot = VectorDot( landToEnemy, landRight );
if ( right_dot > COS_45 )
{
jumpAnimStates.landAnimState = "attack_leap_swipe_right";
jumpAnimStates.landAnimEntry = GetRandomAnimEntry( "attack_leap_swipe_right" );
}
else if ( right_dot < COS_45*-1 )
{
jumpAnimStates.landAnimState = "attack_leap_swipe_left";
jumpAnimStates.landAnimEntry = GetRandomAnimEntry( "attack_leap_swipe_left" );
}
}
}
melee_swipe_static( enemy )
{
self endon( "melee_pain_interrupt" );
if ( !do_single_swipe( "swipe", 2, enemy, 1.0, SWIPE_OFFSET_XY, MAX_SWIPE_DAMAGE_DIST ))
wait 0.05; // ensure at least one frame passes
}
melee_synch_attack( enemy )
{
if ( isDefined( self.xyanimscale ) )
self ScrAgentSetAnimScale( self.xyanimscale, 1.0 );
self.melee_synch = true;
attackAnim = self GetAnimEntry( self.synch_anim_state , 0 );
synchList = enemy get_synch_direction_list( self );
up = AnglesToUp( enemy.angles );
forward = VectorNormalize( enemy.origin - self.synch_attack_pos );
right = VectorCross( forward, up );
attackAngles = AxisToAngles( forward, right, up );
self maps\mp\agents\alien\_alien_anim_utils::turnTowardsVector( AnglesToForward( attackAngles ) );
if ( !IsDefined( enemy ) )
return;
self SetPlayerAngles( attackAngles );
self ScrAgentSetOrientMode( "face angle abs", attackAngles );
self thread synch_attack_anim_lerp( attackAnim, self.synch_attack_pos, attackAngles );
animLabel = synchList[self.synch_attack_index]["attackerAnimLabel"];
self play_synch_attack( self.synch_attack_index, self.synch_anim_state, enemy, animLabel, synchList );
if ( IsDefined( enemy ) )
enemy notify( "synched_attack_over" );
maps\mp\agents\_scriptedagents::PlayAnimNUntilNotetrack( self.synch_anim_state, 2, animLabel, "end" );
self.melee_synch = false;
}
play_synch_attack( attack_index, anim_state, enemy, anim_label, synch_list )
{
level endon( "game_ended" );
foreach ( endNotify in enemy.synch_attack_setup.end_notifies )
enemy endon( endNotify );
endAnim = self GetAnimEntry( anim_state, 2 );
endAnimTime = GetAnimLength( endAnim );
enemy thread enemy_process_synch_attack( self, attack_index, endAnimTime, synch_list );
startAnim = self GetAnimEntry( anim_state, 0 );
enemyAnim = synch_list[attack_index]["enterAnim"];
enemy [[ enemy.synch_attack_setup.begin_attack_func ]]( enemyAnim );
maps\mp\agents\_scriptedagents::PlayAnimNUntilNotetrack( anim_state, 0, anim_label, "end" );
if ( !IsDefined( enemy ) )
return;
enemyAnim = synch_list[attack_index]["loopAnim"];
self thread apply_synch_attack_damage( enemy );
while( IsDefined( enemy ) )
{
enemy [[ enemy.synch_attack_setup.loop_attack_func ]]( enemyAnim );
maps\mp\agents\_scriptedagents::PlayAnimNUntilNotetrack( anim_state, 1, anim_label, "end" );
}
}
apply_synch_attack_damage( enemy )
{
self endon( "death" );
self endon( "enemy" );
enemy endon( "enemy_synch_end_notify" );
enemy endon( "synched_attack_over" );
enemy endon( "death" );
foreach ( endNotify in enemy.synch_attack_setup.end_notifies )
enemy endon( endNotify );
damage_amount = 1.0;
if ( isDefined( self.alien_type ) )
{
min_damage = level.alien_types[ self.alien_type ].attributes[ "synch_min_damage_per_second" ] * level.cycle_damage_scalar;
max_damage = level.alien_types[ self.alien_type ].attributes[ "synch_max_damage_per_second" ] * level.cycle_damage_scalar;
damage_amount = RandomFloatRange( min_damage, max_damage );
}
lastDamageTime = GetTime();
while ( true )
{
currentTime = GetTime();
timeElapsed = ( currentTime - lastDamageTime ) * 0.001;
scaledDamage = damage_amount * timeElapsed;
enemy DoDamage( scaledDamage, self.origin, self, self );
wait 0.1;
lastDamageTime = currentTime;
}
}
synch_attack_anim_lerp( start_anim, begin_pos, begin_angles )
{
self endon( "death" );
startAnimLength = GetAnimLength( start_anim );
lerp_time = Min( 0.2, startAnimLength );
lerpMoveDelta = GetMoveDelta( start_anim, 0, lerp_time / startAnimLength );
if ( IsDefined( self.xyanimscale ) )
lerpMoveDelta *= self.xyanimscale;
lerpMoveOffset = RotateVector( lerpMoveDelta, begin_angles );
self ScrAgentDoAnimLerp( self.origin, begin_pos + lerpMoveOffset, lerp_time );
wait lerp_time;
self ScrAgentSetAnimMode( "anim deltas" );
}
enemy_process_synch_attack( attacker, attackIndex, endAnimTime, synchList )
{
shouldPlayExitAnim = enemy_wait_for_synch_attack_end( attacker, attackIndex );
if ( !IsDefined( self ) )
return;
animName = undefined;
if ( shouldPlayExitAnim )
animName = synchList[attackIndex]["exitAnim"];
self [[ self.synch_attack_setup.end_attack_func ]]( animName, endAnimTime );
if ( IsDefined( self ) )
self.synch_attack_setup.primary_attacker = undefined;
}
enemy_wait_for_synch_attack_end( attacker, attackIndex )
{
self thread enemy_wait_for_synch_invalid_enemy( attacker );
foreach ( endNotify in self.synch_attack_setup.end_notifies )
self thread enemy_wait_for_synch_end_notify( endNotify );
msg = self waittill_any_return( "enemy_synch_end_notify", "synched_attack_over", "enemy_synch_invalid_enemy" );
return ( IsDefined( msg ) && msg != "enemy_synch_end_notify" );
}
enemy_wait_for_synch_end_notify( end_notify )
{
self endon( "enemy_synch_end_notify" );
self endon( "synched_attack_over" );
self endon( "enemy_synch_invalid_enemy" );
self waittill( end_notify );
self notify( "enemy_synch_end_notify" );
}
enemy_wait_for_synch_invalid_enemy( attacker )
{
self endon( "synched_attack_over" );
self endon( "enemy_synch_end_notify" );
self endon( "death" );
level endon( "game_ended" );
wait 0.05;
while ( true )
{
if ( !IsAlive( attacker ) )
break;
if ( !IsDefined( attacker.enemy ) || attacker.enemy != self )
break;
wait 0.05;
}
self notify( "enemy_synch_invalid_enemy" );
}
melee_wall( enemy )
{
MAX_DIST_WALL_MELEE = 800;
MIN_DIST_WALL_MELEE = 168;
assert( IsDefined( self.wall_leap_melee_node ) );
// We're near the player, find a nearby jump node that works for our purposes
target_jump_node = self.wall_leap_melee_node;
self.melee_jumping_to_wall = true;
self maps\mp\agents\alien\_alien_jump::Jump( self.origin, self.angles, target_jump_node.origin, target_jump_node.angles, enemy.origin );
self.melee_jumping_to_wall = false;
if ( !isAlive( enemy ) )
{
return;
}
if ( maps\mp\agents\alien\_alien_think::can_leap_melee( enemy, MAX_DIST_WALL_MELEE, MIN_DIST_WALL_MELEE ) )
{
melee_leap_internal( enemy, "wall" );
}
}
badpath_jump( enemy )
{
assert( isDefined( self.leapEndPos ) );
assert( isDefined( self.leapEndAngles ) );
self.melee_jumping = true;
self maps\mp\agents\alien\_alien_jump::Jump( self.origin, self.angles, self.leapEndPos, self.leapEndAngles, undefined );
self.melee_jumping = false;
}
melee_DoDamage( enemy, melee_type )
{
if ( !isAlive( enemy ) )
{
return;
}
self.melee_success = true;
damage_amount = 1;
if ( isDefined( self.alien_type ) )
{
min_damage = level.alien_types[ self.alien_type ].attributes[ melee_type + "_min_damage" ];
max_damage = level.alien_types[ self.alien_type ].attributes[ melee_type + "_max_damage" ];
if ( self get_alien_type() == "elite" && IsDefined( self.elite_angered ) )
{
min_damage *= maps\mp\agents\alien\_alien_elite::get_angered_damage_scalar();
max_damage *= maps\mp\agents\alien\_alien_elite::get_angered_damage_scalar();
}
damage_amount = RandomFloatRange( min_damage, max_damage );
}
if ( isdefined( self.pet ) && self.pet )
{
if ( is_true( self.upgraded ) )
damage_amount *= 10;
else
damage_amount *= 6;
}
if ( IsPlayer( enemy ) )
{
enemy_blocked = self check_for_block( enemy );
player_meleeing = self check_for_player_meleeing( enemy );
if ( enemy_blocked || player_meleeing )
{
return;
}
else
{
if ( isdefined( enemy.isJuggernaut ) && enemy.isJuggernaut )
{
damage_amount *= 0.65;
Earthquake( 0.25,0.25,enemy.origin,100 );
}
enemy set_damage_viewkick( damage_amount );
enemy PlayLocalSound( "Player_hit_sfx_alien" );
}
}
if ( enemy.model == "mp_laser_drill" && level.script == "mp_alien_dlc3" )
damage_amount *= MP_ALIEN_DLC3_DRILL_MELEE_SCALAR;
enemy DoDamage( damage_amount, self.origin, self, self );
}
set_damage_viewkick( damage_amount )
{
MAX_VIEWKICK = 10;
BASE_VIEWKICK = 2;
DAMAGE_SCALE_BASE = 50;
if ( self maps\mp\alien\_persistence::is_upgrade_enabled( "less_flinch_upgrade" ) )
MAX_VIEWKICK = 0;
additional_viewkick_scale = min( 1, damage_amount / DAMAGE_SCALE_BASE );
additional_viewkick = ( MAX_VIEWKICK - BASE_VIEWKICK ) * additional_viewkick_scale;
viewkick_scale = BASE_VIEWKICK + additional_viewkick;
if( is_chaos_mode() )
{
if( maps\mp\alien\_utility::isPlayingSolo() )
viewkick_scale = ( viewkick_scale / 1.6 );
else
viewkick_scale = ( viewkick_scale / 2.2 );
}
self SetViewKickScale( viewkick_scale );
}
move_back( enemy, force_posture )
{
self endon( "melee_pain_interrupt" );
if ( !IsDefined( force_posture ) )
force_posture = false;
if ( force_posture || self should_move_back( enemy ) )
{
move_back_state = self GetMoveBackState();
move_back_entry = self GetMoveBackEntry( move_back_state );
moveBackAnim = self GetAnimEntry( move_back_state, move_back_entry );
availableMoveBackScale = GetSafeAnimMoveDeltaPercentage( moveBackAnim );
if ( has_room_to_move_back( availableMoveBackScale ) )
{
self.melee_in_move_back = true;
self ScrAgentSetAnimMode( "anim deltas" );
self ScrAgentSetPhysicsMode( "gravity" );
self ScrAgentSetAnimScale( availableMoveBackScale, 1.0 );
self SetAnimState( move_back_state, move_back_entry, 1.0 );
//animLength = GetAnimLength( moveBackAnim );
//wait ( animLength * 0.8 );
self WaitUntilNotetrack( "move_back", "finish" );
self.melee_in_move_back = false;
}
if ( force_posture || should_posture( enemy ) )
{
self.melee_in_posture = true;
self set_alien_emissive( 0.2, 0.8 );
random_entry = GetRandomAnimEntry( "posture" );
self SetAnimState( "posture", random_entry, 1.0 ); // Play the "quick" posturing anim
self ScrAgentSetOrientMode( "face angle abs", self.angles );
self WaitUntilNotetrack( "posture", "end" );
self set_alien_emissive_default( 0.2 );
self.melee_in_posture = false;
}
else if ( should_move_side( enemy ) )
{
self move_side();
}
}
}
move_side()
{
self endon( "melee_pain_interrupt" );
if ( cointoss() )
{
if ( !self try_move_side( "melee_move_side_left" ) )
{
self try_move_side( "melee_move_side_right" );
}
}
else
{
if ( !self try_move_side( "melee_move_side_right" ) )
{
self try_move_side( "melee_move_side_left" );
}
}
}
try_move_side( animState )
{
animEntry = GetRandomAnimEntry( animState );
sideMoveAnim = self GetAnimEntry( animState, animEntry );
availableSideMoveScale = GetSafeAnimMoveDeltaPercentage( sideMoveAnim );
availableSideMoveScale = min( availableSideMoveScale, self.xyanimscale );
if ( availableSideMoveScale > 0.5 )
{
self ScrAgentSetAnimMode( "anim deltas" );
self ScrAgentSetPhysicsMode( "gravity" );
self ScrAgentSetAnimScale( availableSideMoveScale, 1.0 );
self SetAnimState( animState, animEntry, 1.0 );
//animLength = GetAnimLength( sideMoveAnim );
//wait animLength * 1.0;
self WaitUntilNotetrack( "move_side", "finish" );
return true;
}
return false;
}
should_move_back( enemy )
{
MAX_MOVE_BACK_DISTANCE_SQR = 10000.0; // 100.0 * 100.0
if ( isAlive( enemy ) && DistanceSquared( self.origin, enemy.origin ) > MAX_MOVE_BACK_DISTANCE_SQR )
{
return false;
}
return true;
}
should_posture( enemy )
{
return ( isAlive( enemy ) && RandomInt( 100 ) < ALIEN_MELEE_POSTURE_PERCENT );
}
should_move_side( enemy )
{
switch ( maps\mp\alien\_utility::get_alien_type() )
{
case "elite":
case "mammoth":
case "spitter":
case "seeder":
return false;
default:
return ( isAlive( enemy ) && RandomInt( 100 ) < ALIEN_MELEE_MOVE_SIDE_PERCENT );
}
}
has_room_to_move_back( availableMoveBackScale )
{
MIN_MOVE_BACK_SCALE = 0.5;
return availableMoveBackScale >= MIN_MOVE_BACK_SCALE;
}
GetMoveBackState()
{
return "melee_move_back";
}
GetMoveBackEntry( move_back_state )
{
randomInteger = RandomIntRange ( 0, 101 );
runningTotal = 0;
randomIndex = undefined;
for ( i = 0 ; i < level.alienAnimData.alienMoveBackAnimChance.size ; i++ )
{
runningTotal += level.alienAnimData.alienMoveBackAnimChance[ i ];
if ( randomInteger <= runningTotal )
{
randomIndex = i;
break;
}
}
return randomIndex;
}
onDamage( eInflictor, eAttacker, iThatDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset )
{
if ( canDoPain( sMeansOfDeath,eAttacker ) )
self DoPain( iDFlags, iThatDamage, sMeansOfDeath, vDir, sHitLoc );
}
canDoPain( sMeansOfDeath, eAttacker )
{
if ( IsDefined( level.dlc_can_do_pain_override_func ) )
{
painAllowed = [[level.dlc_can_do_pain_override_func]]( "melee" );
if ( !painAllowed )
return false;
}
switch( maps\mp\alien\_utility::get_alien_type() )
{
case "elite":
case "mammoth":
return false;
default:
painIsAvailable = maps\mp\alien\_utility::is_pain_available( eAttacker, sMeansOfDeath );
return ( painIsAvailable && !self.melee_jumping && !self.melee_jumping_to_wall && !self.playing_pain_animation && !self.stateLocked && !self.melee_synch );
}
}
DoPain( iDFlags, iDamage, sMeansOfDeath, vDir, sHitLoc )
{
self endon( "killanimscript" );
self.playing_pain_animation = true;
self notify( "melee_pain_interrupt" );
if ( IsDefined( self.oriented ) && self.oriented )
{
self ScrAgentSetAnimMode( "code_move" );
}
else
{
self ScrAgentSetOrientMode( "face angle abs", self.angles );
self ScrAgentSetAnimMode( "anim deltas" );
}
is_stun = iDFlags & level.iDFLAGS_STUN;
animStateInfo = get_melee_painState_info( iDamage, sMeansOfDeath, is_stun );
animIndex = getMeleePainAnimIndex( animStateInfo[ "anim_state" ], vDir, sHitLoc );
anime = self GetAnimEntry( animStateInfo[ "anim_state" ], animIndex );
self maps\mp\alien\_utility::always_play_pain_sound( anime );
self maps\mp\alien\_utility::register_pain( anime );
self PlayAnimNUntilNotetrack( animStateInfo[ "anim_state" ], animIndex, animStateInfo[ "anim_label" ] );
self.playing_pain_animation = false;
self notify( "pain_finished" );
}
getMeleePainAnimIndex( animState, damageDirection, hitLocation )
{
switch( animState )
{
case "pain_pushback":
return maps\mp\agents\alien\_alien_anim_utils::getPainAnimIndex( "push_back", damageDirection );
case "idle_pain_light":
case "idle_pain_heavy":
return maps\mp\agents\alien\_alien_anim_utils::getPainAnimIndex( "idle", damageDirection, hitLocation );
case "move_back_pain_light":
case "move_back_pain_heavy":
return maps\mp\agents\alien\_alien_anim_utils::getPainAnimIndex( "move_back", damageDirection );
case "melee_pain_light":
case "melee_pain_heavy":
return maps\mp\agents\alien\_alien_anim_utils::getPainAnimIndex( "melee", damageDirection );
default:
AssertMsg( "Unsupported anim state: " + animState );
}
}
check_for_player_meleeing( player )
{
playerForwardVector = anglesToForward( player.angles );
playerToEnemyVector = VectorNormalize( self.origin - player.origin );
dotProduct = VectorDot( playerToEnemyVector, playerForwardVector );
if ( player MeleeButtonPressed() && isDefined( player.meleeStrength ) && player.meleeStrength == 1 && dotProduct > 0.5 )
return true;
else
return false;
}
check_for_block( player )
{
if (!(isPlayer( player )))
return false;
if ( !player.hasriotshield )
return false;
enemy_in_front = false;
enemy_in_back = false;
front_block = false;
back_block = false;
riot_shield = player riotShieldName();
if ( !isdefined( riot_shield ) )
return false;
shield_weapon_health = player GetWeaponAmmoClip( riot_shield );
playerForwardVector = anglesToForward( player.angles );
playerToEnemyVector = VectorNormalize( self.origin - player.origin );
dotProduct = VectorDot( playerToEnemyVector, playerForwardVector );
if ( dotProduct > 0.5 )
{
enemy_in_front = true;
}
if ( dotProduct < -0.5 )
{
enemy_in_back = true;
}
if ( player.hasriotshieldequipped && enemy_in_front && ( shield_weapon_health > 0 ) )
{
front_block = true;
}
else if ( !player.hasriotshieldequipped && enemy_in_back && ( shield_weapon_health > 0 ) )
{
back_block = true;
is_fire_back_block = should_catch_fire( player );
if ( is_fire_back_block )
self thread maps\mp\alien\_damage::catch_alien_on_fire( player, 1, 75 ); //self = alien attacking the player
}
////////////now we know we are in the act of blocking so we remove one ammo from the shield
if ( front_block || back_block )
{
player SetWeaponAmmoClip( riot_shield, ( shield_weapon_health - 1 ));
player notify( "riotshield_block" );
player SetClientOmnvar ( "ui_alien_stowed_riotshield_ammo", shield_weapon_health - 1 );
player PlaySound( "melee_riotshield_impact" );
Earthquake( 0.75,0.5,player.origin, 100 );
if ( self should_snare( player ) )
player maps\mp\alien\_damage::applyAlienSnare();
////////////check the remaining ammo for the riotshield and if we are out then remove riotshield and switch to other weapon
if ( player GetWeaponAmmoClip( riot_shield ) == 0 )
{
if ( player HasWeapon( riot_shield ) )
{
player TakeWeapon( riot_shield );
player.hasRiotShield = false;
player.hasRiotshieldequipped = false;
if ( front_block )
{
player DetachShieldModel( "weapon_riot_shield_iw6", "tag_weapon_right" );
player PlaySound( "melee_riotshield_impact" );
player IPrintLnBold ( &"ALIENS_HANDY_RIOT_DESTROYED" );
}
if ( back_block )
{
player DetachShieldModel( "weapon_riot_shield_iw6", "tag_shield_back" );
player PlaySound( "melee_riotshield_impact" );
player IPrintLnBold ( &"ALIENS_STOWED_RIOT_DESTROYED" );
}
//remove the icon
player setclientomnvar ( "ui_alien_riotshield_equipped",-1 );
}
weapon_list = player GetWeaponsList( "primary" );
Assert( weapon_list.size );
if ( weapon_list.size > 0 && front_block )
{
player SwitchToWeapon( weapon_list[0] );
}
}
return true;
}
return false;
}
should_catch_fire( player )
{
if ( player maps\mp\alien\_persistence::is_upgrade_enabled( "riotshield_back_upgrade" ) && self.alien_type != "spider" && self.alien_type != "kraken_tentacle" && self.alien_type != "kraken" )
return true;
else
return false;
}
melee_clean_up( attacker )
{
self endon( "death" );
self endon( "disconnect" );
attacker_alien_type = attacker maps\mp\alien\_utility::get_alien_type();
attacker waittill( "killanimscript" );
//Reset alien melee related flags
if ( attacker_alien_type == "elite" || attacker_alien_type == "mammoth" )
self.being_charged = false;
}
get_melee_painState_info( iDamage, sMeansOfDeath, is_stun )
{
result = [];
if ( sMeansOfDeath == "MOD_MELEE" )
{
result[ "anim_label" ] = "pain_pushback";
result[ "anim_state" ] = "pain_pushback";
return result;
}
switch( self.melee_type )
{
case "spit":
result[ "anim_state" ] = "idle_pain_light";
result[ "anim_label" ] = "idle_pain";
break;
default:
if ( self.melee_in_move_back )
{
primaryAnimState = "move_back_pain";
result[ "anim_label" ] = "move_back_pain";
}
else if ( self.melee_in_posture )
{
primaryAnimState = "idle_pain";
result[ "anim_label" ] = "idle_pain";
}
else
{
primaryAnimState = "melee_pain";
result[ "anim_label" ] = "melee_pain";
}
result[ "anim_state" ] = self maps\mp\agents\alien\_alien_anim_utils::getPainAnimState( primaryAnimState, iDamage, is_stun );
break;
}
return result;
}