IW4-Dump-Files/animscripts/pain.gsc

1565 lines
39 KiB
Plaintext

#include animscripts\Utility;
#include animscripts\weaponList;
#include common_scripts\utility;
#include animscripts\Combat_Utility;
#using_animtree( "generic_human" );
main()
{
if ( isdefined( self.longDeathStarting ) )
{
// important that we don't run any other animscripts.
self waittill( "killanimscript" );
return;
}
if ( [[ anim.pain_test ]]() )
return;
if ( self.a.disablePain )
return;
self notify( "kill_long_death" );
if ( isdefined( self.a.painTime ) )
self.a.lastPainTime = self.a.painTime;
else
self.a.lastPainTime = 0;
self.a.painTime = gettime();
if ( self.stairsState != "none" )
self.a.painOnStairs = true;
else
self.a.painOnStairs = undefined;
if ( self.a.nextStandingHitDying )
self.health = 1;
dead = false;
stumble = false;
ratio = self.health / self.maxHealth;
// println ("hit at " + self.damagelocation);
self notify( "anim entered pain" );
self endon( "killanimscript" );
// Two pain animations are played. One is a longer, detailed animation with little to do with the actual
// location and direction of the shot, but depends on what pose the character starts in. The other is a
// "hit" animation that is very location-specific, but is just a single pose for the affected bones so it
// can be played easily whichever position the character is in.
animscripts\utility::initialize( "pain" );
self animmode( "gravity" );
//thread [[anim.println]] ("Shot in "+self.damageLocation+" from "+self.damageYaw+" for "+self.damageTaken+" hit points");#/
if ( !isdefined( self.no_pain_sound ) )
self animscripts\face::SayGenericDialogue( "pain" );
if ( self.damageLocation == "helmet" )
self animscripts\death::helmetPop();
else if ( self wasDamagedByExplosive() && randomint( 2 ) == 0 )
self animscripts\death::helmetPop();
if ( isdefined( self.painFunction ) )
{
self [[ self.painFunction ]]();
return;
}
// corner grenade death takes priority over crawling pain
/#
if ( getDvarInt( "scr_forceCornerGrenadeDeath" ) == 1 )
{
if ( self TryCornerRightGrenadeDeath() )
return;
}
#/
if ( crawlingPain() )
return;
if ( specialPain( self.a.special ) )
return;
// if we didn't handle self.a.special, we can't rely on it being accurate after the pain animation we're about to play.
//self.a.special = "none";
//self.specialDeathFunc = undefined;
painAnim = getPainAnim();
/#
if ( getdvarint( "scr_paindebug" ) == 1 )
println( "^2Playing pain: ", painAnim, " ; pose is ", self.a.pose );
#/
playPainAnim( painAnim );
}
initPainFx()
{
level._effect[ "crawling_death_blood_smear" ] = LoadFX( "impacts/blood_smear_decal" );
}
end_script()
{
if ( isdefined( self.damageShieldPain ) )
{
self.damageShieldCounter = undefined;
self.damageShieldPain = undefined;
self.allowpain = true;
// still somewhat risky
if ( !isdefined( self.preDamageShieldIgnoreMe ) )
self.ignoreme = false;
self.preDamageShieldIgnoreMe = undefined;
}
if ( isdefined( self.blockingPain ) )
{
self.blockingPain = undefined;
self.allowPain = true;
}
}
wasDamagedByExplosive()
{
if ( isExplosiveDamageMOD( self.damageMod ) )
return true;
if ( gettime() - anim.lastCarExplosionTime <= 50 )
{
rangesq = anim.lastCarExplosionRange * anim.lastCarExplosionRange * 1.2 * 1.2;
if ( distanceSquared( self.origin, anim.lastCarExplosionDamageLocation ) < rangesq )
{
// assume this exploding car damaged us.
upwardsDeathRangeSq = rangesq * 0.5 * 0.5;
self.mayDoUpwardsDeath = ( distanceSquared( self.origin, anim.lastCarExplosionLocation ) < upwardsDeathRangeSq );
return true;
}
}
return false;
}
maxDamageShieldPainInterval = 1500;
getDamageShieldPainAnim()
{
if ( self.a.pose == "prone" )
return;
if ( isdefined( self.lastAttacker ) && isdefined( self.lastAttacker.team ) && self.lastAttacker.team == self.team )
return;
if ( !isdefined( self.damageShieldCounter ) || ( gettime() - self.a.lastPainTime ) > maxDamageShieldPainInterval )
self.damageShieldCounter = randomintrange( 2, 3 );
if ( isdefined( self.lastAttacker ) && distanceSquared( self.origin, self.lastAttacker.origin ) < squared( 512 ) )
self.damageShieldCounter = 0;
if ( self.damageShieldCounter > 0 )
{
self.damageShieldCounter--;
return;
}
self.damageShieldPain = true;
self.allowpain = false;
if ( self.ignoreme )
self.preDamageShieldIgnoreMe = true;
else
self.ignoreme = true;
if ( usingSidearm() )
animscripts\shared::placeWeaponOn( self.primaryweapon, "right" );
if ( self.a.pose == "crouch" )
return %exposed_crouch_extendedpainA;
painArray = array( %stand_exposed_extendedpain_chest, %stand_exposed_extendedpain_head_2_crouch, %stand_exposed_extendedpain_hip_2_crouch );
return painArray[ randomint( painArray.size ) ];
}
MAX_RUNNING_PAIN_DIST_SQ = ( 64 * 64 );
getPainAnim()
{
if ( self.damageShield && !isdefined( self.disableDamageShieldPain ) )
{
painAnim = getDamageShieldPainAnim();
if ( isdefined( painAnim ) )
return painAnim;
}
if ( isdefined( self.a.onback ) )
{
if ( self.a.pose == "crouch" )
return %back_pain;
else
animscripts\shared::stopOnBack();
}
if ( self.a.pose == "stand" )
{
closeToNode = isdefined( self.node ) && ( distanceSquared( self.origin, self.node.origin ) < MAX_RUNNING_PAIN_DIST_SQ );
if ( !closeToNode && self.a.movement == "run" && ( abs( self getMotionAngle() ) < 60 ) )
return getRunningForwardPainAnim();
self.a.movement = "stop";
return getStandPainAnim();
}
else if ( self.a.pose == "crouch" )
{
self.a.movement = "stop";
return getCrouchPainAnim();
}
else if ( self.a.pose == "prone" )
{
self.a.movement = "stop";
return getPronePainAnim();
}
}
RUN_PAIN_SHORT = 120; // actual animations are 150 but let it run against the wall a bit.
RUN_PAIN_MED = 200;
RUN_PAIN_LONG = 300;
getRunningForwardPainAnim()
{
// 200 units
runPains = [];
allowMedPain = false;
allowLongPain = false;
allowShortPain = false;
if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_LONG, 0, 0 ) ) ) )
{
allowLongPain = true;
allowMedPain = true;
}
else if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_MED, 0, 0 ) ) ) )
{
allowMedPain = true;
}
if ( allowLongPain )
{
runPains[ runPains.size ] = %run_pain_leg;
runPains[ runPains.size ] = %run_pain_shoulder;
runPains[ runPains.size ] = %run_pain_stomach_stumble;
runPains[ runPains.size ] = %run_pain_head;
}
// randomize medium with long pains
if ( allowMedPain )
{
runPains[ runPains.size ] = %run_pain_fallonknee_02;
runPains[ runPains.size ] = %run_pain_stomach;
runPains[ runPains.size ] = %run_pain_stumble;
runPains[ runPains.size ] = %run_pain_stomach_fast;
runPains[ runPains.size ] = %run_pain_leg_fast;
runPains[ runPains.size ] = %run_pain_fall;
}
// short pains are a back up only
else if ( self mayMoveToPoint( self localToWorldCoords( ( RUN_PAIN_SHORT, 0, 0 ) ) ) )
{
// drop check
runPains[ runPains.size ] = %run_pain_fallonknee;
runPains[ runPains.size ] = %run_pain_fallonknee_03;
}
if ( !runPains.size )
{
self.a.movement = "stop";
return getStandPainAnim();
}
return runPains[ randomint( runPains.size ) ];
}
getStandPistolPainAnim()
{
painArray = [];
if ( self damageLocationIsAny( "torso_upper", "torso_lower", "left_arm_upper", "right_arm_upper", "neck" ) )
painArray[ painArray.size ] = %pistol_stand_pain_chest;
if ( self damageLocationIsAny( "torso_lower", "left_leg_upper", "right_leg_upper" ) )
painArray[ painArray.size ] = %pistol_stand_pain_groin;
if ( self damageLocationIsAny( "head", "neck" ) )
painArray[ painArray.size ] = %pistol_stand_pain_head;
if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper", "torso_upper" ) )
painArray[ painArray.size ] = %pistol_stand_pain_leftshoulder;
if ( self damageLocationIsAny( "right_arm_lower", "right_arm_upper", "torso_upper" ) )
painArray[ painArray.size ] = %pistol_stand_pain_rightshoulder;
if ( painArray.size < 2 )
painArray[ painArray.size ] = %pistol_stand_pain_chest;
if ( painArray.size < 2 )
painArray[ painArray.size ] = %pistol_stand_pain_groin;
assertex( painArray.size > 0, painArray.size );
return painArray[ randomint( painArray.size ) ];
}
getStandPainAnim()
{
if ( usingSideArm() )
return getStandPistolPainAnim();
painArray = [];
extendedPainArray = [];
if ( self damageLocationIsAny( "torso_upper", "torso_lower" ) )
{
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_gut;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_stomach;
}
if ( self damageLocationIsAny( "torso_upper", "head", "helmet", "neck" ) )
{
painArray[ painArray.size ] = %exposed_pain_face;
painArray[ painArray.size ] = %stand_exposed_extendedpain_neck;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_head_2_crouch;
}
if ( self damageLocationIsAny( "right_arm_upper", "right_arm_lower" ) )
painArray[ painArray.size ] = %exposed_pain_right_arm;
if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper" ) )
{
painArray[ painArray.size ] = %stand_exposed_extendedpain_shoulderswing;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_shoulder_2_crouch;
}
if ( self damageLocationIsAny( "torso_lower", "left_leg_upper", "right_leg_upper" ) )
{
painArray[ painArray.size ] = %exposed_pain_groin;
painArray[ painArray.size ] = %stand_exposed_extendedpain_hip;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_hip_2_crouch;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_feet_2_crouch;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_stomach;
}
if ( self damageLocationIsAny( "left_foot", "right_foot", "left_leg_lower", "right_leg_lower" ) )
{
painArray[ painArray.size ] = %stand_exposed_extendedpain_thigh;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_feet_2_crouch;
}
// default, only exposed pain that takes the AI to ground. Other ones look a bit awkward for getting hit by a bullet
if ( painArray.size < 2 )
{
if ( !self.a.disableLongDeath )
{
painArray[ painArray.size ] = %exposed_pain_2_crouch;
painArray[ painArray.size ] = %stand_extendedpainB;
}
else
{
painArray[ painArray.size ] = %exposed_pain_right_arm;
painArray[ painArray.size ] = %exposed_pain_face;
painArray[ painArray.size ] = %exposed_pain_groin;
}
}
if ( extendedPainArray.size < 2 )
{
extendedPainArray[ extendedPainArray.size ] = %stand_extendedpainC;
extendedPainArray[ extendedPainArray.size ] = %stand_exposed_extendedpain_chest;
}
if ( !self.damageShield && !self.a.disableLongDeath )
{
index = randomint( painArray.size + extendedPainArray.size );
if ( index < painArray.size )
return painArray[index];
else
return extendedPainArray[ index - painArray.size ];
}
assertex( painArray.size > 0, painArray.size );
return painArray[ randomint( painArray.size ) ];
}
removeBlockedAnims( array )
{
newArray = [];
for ( index = 0; index < array.size; index++ )
{
painAnim = array[ index ];
time = 1;
if ( animHasNoteTrack( painAnim, "code_move" ) )
time = getNotetrackTimes( painAnim, "code_move" )[0];
localDeltaVector = getMoveDelta( painAnim, 0, time );
endPoint = self localToWorldCoords( localDeltaVector );
if ( self mayMoveToPoint( endPoint, true, true ) )
newArray[ newArray.size ] = painAnim;
}
return newArray;
}
getCrouchPainAnim()
{
painArray = [];
if ( !self.damageShield && !self.a.disableLongDeath )
painArray[ painArray.size ] = %exposed_crouch_extendedpainA;
painArray[ painArray.size ] = %exposed_crouch_pain_chest;
painArray[ painArray.size ] = %exposed_crouch_pain_headsnap;
painArray[ painArray.size ] = %exposed_crouch_pain_flinch;
if ( damageLocationIsAny( "left_hand", "left_arm_lower", "left_arm_upper" ) )
painArray[ painArray.size ] = %exposed_crouch_pain_left_arm;
if ( damageLocationIsAny( "right_hand", "right_arm_lower", "right_arm_upper" ) )
painArray[ painArray.size ] = %exposed_crouch_pain_right_arm;
assertex( painArray.size > 0, painArray.size );
return painArray[ randomint( painArray.size ) ];
}
getPronePainAnim()
{
if ( randomint( 2 ) == 0 )
return %prone_reaction_A;
else
return %prone_reaction_B;
}
playPainAnim( painAnim )
{
// TEMP make all pain faster
// rate = 1.5;
rate = 1;
self setFlaggedAnimKnobAllRestart( "painanim", painAnim, %body, 1, .1, rate );
if ( self.a.pose == "prone" )
self UpdateProne( %prone_legs_up, %prone_legs_down, 1, 0.1, 1 );
if ( animHasNotetrack( painAnim, "start_aim" ) )
{
self thread notifyStartAim( "painanim" );
self endon( "start_aim" );
}
if ( animHasNotetrack( painAnim, "code_move" ) )
self animscripts\shared::DoNoteTracks( "painanim" );
self animscripts\shared::DoNoteTracks( "painanim" );
}
notifyStartAim( animFlag )
{
self endon( "killanimscript" );
self waittillmatch( animFlag, "start_aim" );
self notify( "start_aim" );
}
specialPainBlocker()
{
self endon( "killanimscript" );
assert( self.allowPain );
self.blockingPain = true;
self.allowPain = false;
wait 0.5;
self.blockingPain = undefined;
self.allowPain = true;
}
// Special pain is for corners, rambo behavior, mg42's, anything out of the ordinary stand, crouch and prone.
// It returns true if it handles the pain for the special animation state, or false if it wants the regular
// pain function to handle it.
specialPain( anim_special )
{
if ( anim_special == "none" )
return false;
self.a.special = "none";
self thread specialPainBlocker();
switch( anim_special )
{
case "cover_left":
if ( self.a.pose == "stand" )
{
painArray = [];
painArray[ painArray.size ] = %corner_standl_painB;// groin hit
painArray[ painArray.size ] = %corner_standl_painC;// chest hit
painArray[ painArray.size ] = %corner_standl_painD;// left leg hit
painArray[ painArray.size ] = %corner_standl_painE;// right leg hit
DoPainFromArray( painArray );
handled = true;
}
else if ( self.a.pose == "crouch" )
{
painArray = [];
painArray[ painArray.size ] = %CornerCrL_painB;
DoPainFromArray( painArray );
handled = true;
}
else
{
handled = false;
}
break;
case "cover_right":
if ( self.a.pose == "stand" )
{
painArray = [];
painArray[ 0 ] = %corner_standr_pain;
painArray[ 1 ] = %corner_standr_painB;
painArray[ 2 ] = %corner_standr_painC;
DoPainFromArray( painArray );
handled = true;
}
else if ( self.a.pose == "crouch" )
{
painArray = [];
painArray[ painArray.size ] = %CornerCrR_alert_painA;
painArray[ painArray.size ] = %CornerCrR_alert_painC;
DoPainFromArray( painArray );
handled = true;
}
else
{
handled = false;
}
break;
case "cover_right_stand_A":
//DoPain( %corner_standR_pain_A_2_alert );
handled = false;
break;
case "cover_right_stand_B":
DoPain( %corner_standR_pain_B_2_alert );
handled = true;
break;
case "cover_left_stand_A":
DoPain( %corner_standL_pain_A_2_alert );
handled = true;
break;
case "cover_left_stand_B":
DoPain( %corner_standL_pain_B_2_alert );
handled = true;
break;
/*
// these are just exposed crouch poses
case "cover_right_crouch_A":
case "cover_right_crouch_B":
case "cover_left_crouch_A":
case "cover_left_crouch_B":
handled = false;
break;
*/
case "cover_crouch":
painArray = [];
painArray[painArray.size] = %covercrouch_pain_right;
painArray[painArray.size] = %covercrouch_pain_front;
painArray[painArray.size] = %covercrouch_pain_left_3;
DoPainFromArray(painArray);
handled = true;
break;
case "cover_stand":
painArray = [];
painArray[ painArray.size ] = %coverstand_pain_groin;
painArray[ painArray.size ] = %coverstand_pain_leg;
DoPainFromArray( painArray );
handled = true;
break;
case "cover_stand_aim":
painArray = [];
painArray[ painArray.size ] = %coverstand_pain_aim_2_hide_01;
painArray[ painArray.size ] = %coverstand_pain_aim_2_hide_02;
DoPainFromArray( painArray );
handled = true;
break;
case "cover_crouch_aim":
DoPain( %covercrouch_pain_aim_2_hide_01 );
handled = true;
break;
case "saw":
if ( self.a.pose == "stand" )
painAnim = %saw_gunner_pain;
else if ( self.a.pose == "crouch" )
painAnim = %saw_gunner_lowwall_pain_02;
else
painAnim = %saw_gunner_prone_pain;
self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 );
self animscripts\shared::DoNoteTracks( "painanim" );
handled = true;
break;
case "mg42":
mg42pain( self.a.pose );
handled = true;
break;
case "minigun":
handled = false;
break;
case "corner_right_martyrdom":
handled = ( self TryCornerRightGrenadeDeath() );
break;
case "rambo_left":
case "rambo_right":
case "rambo":
case "dying_crawl":
handled = false;
break;
default:
println( "Unexpected anim_special value : " + anim_special + " in specialPain." );
handled = false;
}
return handled;
}
painDeathNotify()
{
self endon( "death" );
// it isn't safe to notify "pain_death" from the start of an animscript.
// this can cause level script to run, which might cause things with this AI to change while the animscript is starting
// and this can screw things up in unexpected ways.
// take my word for it.
wait .05;
self notify( "pain_death" );
}
DoPainFromArray( painArray )
{
painAnim = painArray[ randomint( painArray.size ) ];
self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 );
self animscripts\shared::DoNoteTracks( "painanim" );
}
DoPain( painAnim )
{
self setflaggedanimknob( "painanim", painAnim, 1, .3, 1 );
self animscripts\shared::DoNoteTracks( "painanim" );
}
mg42pain( pose )
{
// assertmsg("mg42 pain anims not implemented yet");//scripted_mg42gunner_pain
/#
assertEx( isdefined( level.mg_animmg ), "You're missing maps\\_mganim::main(); Add it to your level." );
{
println( " maps\\_mganim::main();" );
return;
}
#/
self setflaggedanimknob( "painanim", level.mg_animmg[ "pain_" + pose ], 1, .1, 1 );
self animscripts\shared::DoNoteTracks( "painanim" );
}
// This is to stop guys from taking off running if they're interrupted during pain. This used to happen when
// guys were running when they entered pain, but didn't play a special running pain (eg because they were
// running sideways). It resulted in a running pain or death being played when they were shot again.
waitSetStop( timetowait, killmestring )
{
self endon( "killanimscript" );
self endon( "death" );
if ( isDefined( killmestring ) )
self endon( killmestring );
wait timetowait;
self.a.movement = "stop";
}
maxCrawlPainHealth = 100;
crawlingPain()
{
if ( self.a.disableLongDeath || self.dieQuietly || self.damageShield )
return false;
if ( self.stairsState != "none" )
return false;
if ( isdefined( self.a.onback ) )
return false;
/#
if ( getDvarInt( "scr_forceCrawl" ) == 1 )
self.forceLongDeath = 1;
#/
if ( isdefined( self.forceLongDeath ) )
{
self.health = 10;
self thread crawlingPistol();
self waittill( "killanimscript" );
return true;
}
transAnims[ "prone" ] = array( %dying_crawl_2_back );
transAnims[ "stand" ] = array( %dying_stand_2_back_v1, %dying_stand_2_back_v2 );
transAnims[ "crouch" ] = array( %dying_crouch_2_back );
self.a.crawlingPainTransAnim = transAnims[self.a.pose][ randomint( transAnims[self.a.pose].size ) ];
if ( !isCrawlDeltaAllowed( self.a.crawlingPainTransAnim ) )
return false;
if ( self.health > maxCrawlPainHealth )
return false;
legHit = self damageLocationIsAny( "left_leg_upper", "left_leg_lower", "right_leg_upper", "right_leg_lower", "left_foot", "right_foot" );
if ( legHit && self.health < self.maxhealth * .4 )
{
if ( gettime() < anim.nextCrawlingPainTimeFromLegDamage )
return false;
}
else
{
if ( anim.numDeathsUntilCrawlingPain > 0 )
return false;
if ( gettime() < anim.nextCrawlingPainTime )
return false;
}
/*if ( self.a.movement != "stop" )
return false;*/
if ( isDefined( self.deathFunction ) )
return false;
foreach ( player in level.players )
{
if ( distance( self.origin, player.origin ) < 175 )
return false;
}
if ( self damageLocationIsAny( "head", "helmet", "gun", "right_hand", "left_hand" ) )
return false;
if ( usingSidearm() )
return false;
// we'll wait a bit to see if this crawling pain will really succeed.
// in the meantime, don't start any other ones.
anim.nextCrawlingPainTime = gettime() + 3000;
anim.nextCrawlingPainTimeFromLegDamage = gettime() + 3000;
// needs to be threaded
self thread crawlingPistol();
self waittill( "killanimscript" );
return true;
}
isCrawlDeltaAllowed( theanim )
{
if ( isdefined( self.a.force_num_crawls ) )
return true;
delta = getMoveDelta( theanim, 0, 1 );
endPoint = self localToWorldCoords( delta );
return self mayMoveToPoint( endPoint );
}
initCrawlingPistolAnims()
{
self.a.array = [];
self.a.array[ "stand_2_crawl" ] = array( %dying_stand_2_crawl_v1, %dying_stand_2_crawl_v2, %dying_stand_2_crawl_v3 );
self.a.array[ "crouch_2_crawl" ] = array( %dying_crouch_2_crawl );
self.a.array[ "crawl" ] = %dying_crawl;
self.a.array[ "death" ] = array( %dying_crawl_death_v1, %dying_crawl_death_v2 );
self.a.array[ "back_idle" ] = %dying_back_idle;
self.a.array[ "back_idle_twitch" ] = array( %dying_back_twitch_A, %dying_back_twitch_B );
self.a.array[ "back_crawl" ] = %dying_crawl_back;
self.a.array[ "back_fire" ] = %dying_back_fire;
self.a.array[ "back_death" ] = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3 );
if ( isdefined( self.crawlingPainAnimOverrideFunc ) )
[[ self.crawlingPainAnimOverrideFunc ]]();
}
crawlingPistol()
{
// don't end on killanimscript. pain.gsc will abort if self.crawlingPistolStarting is true.
self endon( "kill_long_death" );
self endon( "death" );
initCrawlingPistolAnims();
self thread preventPainForAShortTime( "crawling" );
self.a.special = "none";
self.specialDeathFunc = undefined;
self thread painDeathNotify();
//notify ac130 missions that a guy is crawling so context sensative dialog can be played
level notify( "ai_crawling", self );
self thread crawling_stab_achievement();
self setAnimKnobAll( %dying, %body, 1, 0.1, 1 );
// dyingCrawl() returns false if we die without turning around
if ( !self dyingCrawl() )
return;
self setFlaggedAnimKnob( "transition", self.a.crawlingPainTransAnim, 1, 0.5, 1 );
self animscripts\shared::DoNoteTracksIntercept( "transition", ::handleBackCrawlNotetracks );
assert( isdefined( self.a.onback ) );
self.a.special = "dying_crawl";
self thread dyingCrawlBackAim();
if ( isdefined( self.enemy ) )
self setLookAtEntity( self.enemy );
decideNumCrawls();
while ( shouldKeepCrawling() )
{
crawlAnim = animArray( "back_crawl" );
if ( !self isCrawlDeltaAllowed( crawlAnim ) )
break;
self setFlaggedAnimKnobRestart( "back_crawl", crawlAnim, 1, 0.1, 1.0 );
self animscripts\shared::DoNoteTracksIntercept( "back_crawl", ::handleBackCrawlNotetracks );
}
self.desiredTimeOfDeath = gettime() + randomintrange( 4000, 20000 );
while ( shouldStayAlive() )
{
if ( self canSeeEnemy() && self aimedSomewhatAtEnemy() )
{
backAnim = animArray( "back_fire" );
self setFlaggedAnimKnobRestart( "back_idle_or_fire", backAnim, 1, 0.2, 1.0 );
self animscripts\shared::DoNoteTracks( "back_idle_or_fire" );
}
else
{
backAnim = animArray( "back_idle" );
if ( randomfloat( 1 ) < .4 )
backAnim = animArrayPickRandom( "back_idle_twitch" );
self setFlaggedAnimKnobRestart( "back_idle_or_fire", backAnim, 1, 0.1, 1.0 );
timeRemaining = getAnimLength( backAnim );
while ( timeRemaining > 0 )
{
if ( self canSeeEnemy() && self aimedSomewhatAtEnemy() )
break;
interval = 0.5;
if ( interval > timeRemaining )
{
interval = timeRemaining;
timeRemaining = 0;
}
else
{
timeRemaining -= interval;
}
self animscripts\shared::DoNoteTracksForTime( interval, "back_idle_or_fire" );
}
}
}
self notify( "end_dying_crawl_back_aim" );
self clearAnim( %dying_back_aim_4_wrapper, .3 );
self clearAnim( %dying_back_aim_6_wrapper, .3 );
self.deathanim = animArrayPickRandom( "back_death" );
self killWrapper();
self.a.special = "none";
self.specialDeathFunc = undefined;
}
crawling_stab_achievement()
{
if ( self.team == "allies" )
return;
self endon( "end_dying_crawl_back_aim" );
self waittill( "death", attacker, type );
if ( !isdefined( self ) || !isdefined( attacker ) || !isplayer( attacker ) )
return;
// if ( type == "MOD_MELEE" )
// maps\_utility::giveachievement_wrapper( "NO_REST_FOR_THE_WEARY" );
}
shouldStayAlive()
{
if ( !enemyIsInGeneralDirection( anglesToForward( self.angles ) ) )
return false;
return gettime() < self.desiredTimeOfDeath;
}
dyingCrawl()
{
if( !isdefined( self.forceLongDeath ) )
{
if ( self.a.pose == "prone" )
return true;
if ( self.a.movement == "stop" )
{
if ( randomfloat( 1 ) < .4 ) // chance of randomness
{
if ( randomfloat( 1 ) < .5 )
return true;
}
else
{
// if hit from front, return true
if ( abs( self.damageYaw ) > 90 )
return true;
}
}
else
{
// if we're not stopped, we want to fall in the direction of movement
// so return true if moving backwards
if ( abs( self getMotionAngle() ) > 90 )
return true;
}
}
if ( self.a.pose != "prone" )
{
fallAnim = animArrayPickRandom( self.a.pose + "_2_crawl" );
if ( !self isCrawlDeltaAllowed( fallAnim ) )
return true;
self thread dyingCrawlBloodSmear();
self setFlaggedAnimKnob( "falling", fallAnim, 1, 0.5, 1 );
self animscripts\shared::DoNoteTracks( "falling" );
assert( self.a.pose == "prone" );
}
else
{
self thread dyingCrawlBloodSmear();
}
self.a.crawlingPainTransAnim = %dying_crawl_2_back;
self.a.special = "dying_crawl";
decideNumCrawls();
while ( shouldKeepCrawling() )
{
crawlAnim = animArray( "crawl" );
if ( !self isCrawlDeltaAllowed( crawlAnim ) )
return true;
if ( isdefined( self.custom_crawl_sound ) )
{
self playsound( self.custom_crawl_sound );
}
self setFlaggedAnimKnobRestart( "crawling", crawlAnim, 1, 0.1, 1.0 );
self animscripts\shared::DoNoteTracks( "crawling" );
}
self notify( "done_crawling" );
// check if target is in cone to shoot
if ( !isdefined( self.forceLongDeath ) && enemyIsInGeneralDirection( anglesToForward( self.angles ) * - 1 ) )
return true;
deathanim = animArrayPickRandom( "death" );
// this particular death animation is long enough that we want it to be interruptible
if( deathanim != %dying_crawl_death_v2 )
{
// all the others are short so we don't want them to be interruptible
self.a.nodeath = true;
}
animscripts\death::playDeathAnim( deathanim );
self killWrapper();
self.a.special = "none";
self.specialDeathFunc = undefined;
return false;
}
dyingCrawlBloodSmear()
{
self endon( "death" );
if ( self.a.pose != "prone" )
{
while( 1 )
{
self waittill( "falling", note );
if ( IsSubStr( note, "bodyfall" ) )
break;
}
}
origintag = "J_SpineLower";
angletag = "tag_origin";
fx_rate = .25;
fx = level._effect[ "crawling_death_blood_smear" ];
if ( isdefined( self.a.crawl_fx_rate ) )
fx_rate = self.a.crawl_fx_rate;
if( isdefined( self.a.crawl_fx ) )
fx = level._effect[ self.a.crawl_fx ];
while( fx_rate )
{
org = self gettagorigin( origintag );
angles = self GetTagAngles( angletag );
forward = anglestoright( angles );
up = anglestoforward( ( 270, 0, 0 ) );
playfx( fx, org, up, forward );
wait( fx_rate );
}
}
dyingCrawlBackAim()
{
self endon( "kill_long_death" );
self endon( "death" );
self endon( "end_dying_crawl_back_aim" );
if ( isdefined( self.dyingCrawlAiming ) )
return;
self.dyingCrawlAiming = true;
self setAnimLimited( %dying_back_aim_4, 1, 0 );
self setAnimLimited( %dying_back_aim_6, 1, 0 );
prevyaw = 0;
while ( 1 )
{
aimyaw = self getYawToEnemy();
diff = AngleClamp180( aimyaw - prevyaw );
if ( abs( diff ) > 3 )
diff = sign( diff ) * 3;
aimyaw = AngleClamp180( prevyaw + diff );
if ( aimyaw < 0 )
{
if ( aimyaw < - 45.0 )
aimyaw = -45.0;
weight = aimyaw / - 45.0;
self setAnim( %dying_back_aim_4_wrapper, weight, .05 );
self setAnim( %dying_back_aim_6_wrapper, 0, .05 );
}
else
{
if ( aimyaw > 45.0 )
aimyaw = 45.0;
weight = aimyaw / 45.0;
self setAnim( %dying_back_aim_6_wrapper, weight, .05 );
self setAnim( %dying_back_aim_4_wrapper, 0, .05 );
}
prevyaw = aimyaw;
wait .05;
}
}
startDyingCrawlBackAimSoon()
{
self endon( "kill_long_death" );
self endon( "death" );
wait 0.5;
self thread dyingCrawlBackAim();
}
handleBackCrawlNotetracks( note )
{
if ( note == "fire_spray" )
{
if ( !self canSeeEnemy() )
return true;
if ( !self aimedSomewhatAtEnemy() )
return true;
self shootEnemyWrapper();
return true;
}
else if ( note == "pistol_pickup" )
{
self thread startDyingCrawlBackAimSoon();
return false;
}
return false;
}
aimedSomewhatAtEnemy()
{
assert( isdefined( self.enemy ) );
enemyShootAtPos = self.enemy getShootAtPos();
weaponAngles = self getMuzzleAngle();
anglesToEnemy = vectorToAngles( enemyShootAtPos - self getMuzzlePos() );
absyawdiff = AbsAngleClamp180( weaponAngles[ 1 ] - anglesToEnemy[ 1 ] );
if ( absyawdiff > anim.painYawDiffFarTolerance )
{
if ( distanceSquared( self getEye(), enemyShootAtPos ) > anim.painYawDiffCloseDistSQ || absyawdiff > anim.painYawDiffCloseTolerance )
return false;
}
return AbsAngleClamp180( weaponAngles[ 0 ] - anglesToEnemy[ 0 ] ) <= anim.painPitchDiffTolerance;
}
enemyIsInGeneralDirection( dir )
{
if ( !isdefined( self.enemy ) )
return false;
toenemy = vectorNormalize( self.enemy getShootAtPos() - self getEye() );
return( vectorDot( toenemy, dir ) > 0.5 );// cos( 60 ) = 0.5
}
preventPainForAShortTime( type )
{
self endon( "kill_long_death" );
self endon( "death" );
self.flashBangImmunity = true;
self.longDeathStarting = true;
self.a.doingLongDeath = true;
self notify( "long_death" );
self.health = 10000;// also prevent death
self.threatbias = self.threatbias - 2000;
// during this time, we won't be interrupted by more pain.
// this increases the chances of the crawling pain succeeding.
wait .75;
// important that we die the next time we get hit,
// instead of maybe going into pain and coming out and going into combat or something
if ( self.health > 1 )
self.health = 1;
// important that we wait a bit in case we're about to start pain later in this frame
wait .05;
self.longDeathStarting = undefined;
self.a.mayOnlyDie = true;// we've probably dropped our weapon and stuff; we must not do any other animscripts but death!
if ( type == "crawling" )
{
wait 1.0;
// we've essentially succeeded in doing a crawling pain.
if ( isdefined( level.player ) && distanceSquared( self.origin, level.player.origin ) < 1024 * 1024 )
{
anim.numDeathsUntilCrawlingPain = randomintrange( 10, 30 );
anim.nextCrawlingPainTime = gettime() + randomintrange( 15000, 60000 );
}
else
{
anim.numDeathsUntilCrawlingPain = randomintrange( 5, 12 );
anim.nextCrawlingPainTime = gettime() + randomintrange( 5000, 25000 );
}
anim.nextCrawlingPainTimeFromLegDamage = gettime() + randomintrange( 7000, 13000 );
/#
if ( getDebugDvarInt( "scr_crawldebug" ) == 1 )
{
thread printLongDeathDebugText( self.origin + ( 0, 0, 64 ), "crawl death" );
return;
}
#/
}
else if ( type == "corner_grenade" )
{
wait 1.0;
// we've essentially succeeded in doing a corner grenade death.
if ( isdefined( level.player ) && distanceSquared( self.origin, level.player.origin ) < 700 * 700 )
{
anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 10, 30 );
anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 15000, 60000 );
}
else
{
anim.numDeathsUntilCornerGrenadeDeath = randomintrange( 5, 12 );
anim.nextCornerGrenadeDeathTime = gettime() + randomintrange( 5000, 25000 );
}
/#
if ( getDebugDvarInt( "scr_cornergrenadedebug" ) == 1 )
{
thread printLongDeathDebugText( self.origin + ( 0, 0, 64 ), "grenade death" );
return;
}
#/
}
}
/#
printLongDeathDebugText( loc, text )
{
for ( i = 0; i < 100; i++ )
{
print3d( loc, text );
wait .05;
}
}
#/
decideNumCrawls()
{
if( isdefined( self.a.force_num_crawls ) )
self.a.numCrawls = self.a.force_num_crawls;
else
self.a.numCrawls = randomIntRange( 1, 5 );
}
shouldKeepCrawling()
{
// TODO: player distance checks, etc...
assert( isDefined( self.a.numCrawls ) );
if ( !self.a.numCrawls )
{
self.a.numCrawls = undefined;
return false;
}
self.a.numCrawls--;
return true;
}
TryCornerRightGrenadeDeath()
{
/#
if ( getDvarInt( "scr_forceCornerGrenadeDeath" ) == 1 )
{
self thread CornerRightGrenadeDeath();
self waittill( "killanimscript" );
return true;
}
#/
if ( anim.numDeathsUntilCornerGrenadeDeath > 0 )
return false;
if ( gettime() < anim.nextCornerGrenadeDeathTime )
return false;
if ( self.a.disableLongDeath || self.dieQuietly || self.damageShield )
return false;
if ( isDefined( self.deathFunction ) )
return false;
if ( distance( self.origin, level.player.origin ) < 175 )
return false;
// we'll wait a bit to see if this crawling pain will really succeed.
// in the meantime, don't start any other ones.
anim.nextCornerGrenadeDeathTime = gettime() + 3000;
self thread CornerRightGrenadeDeath();
self waittill( "killanimscript" );
return true;
}
CornerRightGrenadeDeath()
{
self endon( "kill_long_death" );
self endon( "death" );
self thread painDeathNotify();
self thread preventPainForAShortTime( "corner_grenade" );
self thread maps\_utility::set_battlechatter( false );
self.threatbias = -1000;// no need for AI to target me
self setFlaggedAnimKnobAllRestart( "corner_grenade_pain", %corner_standR_death_grenade_hit, %body, 1, .1 );
//wait getAnimLength( %corner_standR_death_grenade_hit ) * 0.2;
self waittillmatch( "corner_grenade_pain", "dropgun" );
self animscripts\shared::DropAllAIWeapons();
self waittillmatch( "corner_grenade_pain", "anim_pose = \"back\"" );
animscripts\shared::noteTrackPoseBack();
self waittillmatch( "corner_grenade_pain", "grenade_left" );
model = getWeaponModel( "fraggrenade" );
self attach( model, "tag_inhand" );
self.deathFunction = ::prematureCornerGrenadeDeath;
self waittillmatch( "corner_grenade_pain", "end" );
desiredDeathTime = gettime() + randomintrange( 25000, 60000 );
self setFlaggedAnimKnobAllRestart( "corner_grenade_idle", %corner_standR_death_grenade_idle, %body, 1, .2 );
self thread watchEnemyVelocity();
while ( !enemyIsApproaching() )
{
if ( gettime() >= desiredDeathTime )
break;
self animscripts\shared::DoNoteTracksForTime( 0.1, "corner_grenade_idle" );
}
dropAnim = %corner_standR_death_grenade_slump;
self setFlaggedAnimKnobAllRestart( "corner_grenade_release", dropAnim, %body, 1, .2 );
dropTimeArray = getNotetrackTimes( dropAnim, "grenade_drop" );
assert( dropTimeArray.size == 1 );
dropTime = dropTimeArray[ 0 ] * getAnimLength( dropAnim );
wait dropTime - 1.0;
self animscripts\death::PlayDeathSound();
wait 0.7;
self.deathFunction = ::waitTillGrenadeDrops;
velocity = ( 0, 0, 30 ) - anglesToRight( self.angles ) * 70;
self CornerDeathReleaseGrenade( velocity, randomfloatrange( 2.0, 3.0 ) );
wait .05;
self detach( model, "tag_inhand" );
self thread killSelf();
}
CornerDeathReleaseGrenade( velocity, fusetime )
{
releasePoint = self getTagOrigin( "tag_inhand" );
// avoid dropping under the floor.
releasePointLifted = releasePoint + ( 0, 0, 20 );
releasePointDropped = releasePoint - ( 0, 0, 20 );
trace = bullettrace( releasePointLifted, releasePointDropped, false, undefined );
if ( trace[ "fraction" ] < .5 )
releasePoint = trace[ "position" ];
surfaceType = "default";
if ( trace[ "surfacetype" ] != "none" )
surfaceType = trace[ "surfacetype" ];
// play the grenade drop sound because we're probably not dropping it with enough velocity for it to play it normally
thread playSoundAtPoint( "grenade_bounce_" + surfaceType, releasePoint );
self.grenadeWeapon = "fraggrenade";
self magicGrenadeManual( releasePoint, velocity, fusetime );
}
playSoundAtPoint( alias, origin )
{
org = spawn( "script_origin", origin );
org playsound( alias, "sounddone" );
org waittill( "sounddone" );
org delete();
}
killSelf()
{
self.a.nodeath = true;
self killWrapper();
self startragdoll();
wait .1;
self notify( "grenade_drop_done" );
}
killWrapper()
{
// Set in maps\_spawner.gsc, mainly for SpecOps
// This helps ensure the kill is done by the player if a player is the one who put the Ai into the long-death
if ( IsDefined( self.last_dmg_player ) )
{
self Kill( self.origin, self.last_dmg_player );
}
else
{
self Kill();
}
}
enemyIsApproaching()
{
if ( !isdefined( self.enemy ) )
return false;
if ( distanceSquared( self.origin, self.enemy.origin ) > 384 * 384 )
return false;
if ( distanceSquared( self.origin, self.enemy.origin ) < 128 * 128 )
return true;
predictedEnemyPos = self.enemy.origin + self.enemyVelocity * 3.0;
nearestPos = self.enemy.origin;
if ( self.enemy.origin != predictedEnemyPos )
nearestPos = pointOnSegmentNearestToPoint( self.enemy.origin, predictedEnemyPos, self.origin );
if ( distanceSquared( self.origin, nearestPos ) < 128 * 128 )
return true;
return false;
}
prematureCornerGrenadeDeath()
{
deathArray = array( %dying_back_death_v1, %dying_back_death_v2, %dying_back_death_v3, %dying_back_death_v4 );
deathAnim = deathArray[ randomint( deathArray.size ) ];
self animscripts\death::PlayDeathSound();
self setFlaggedAnimKnobAllRestart( "corner_grenade_die", deathAnim, %body, 1, .2 );
velocity = getGrenadeDropVelocity();
self CornerDeathReleaseGrenade( velocity, 3.0 );
model = getWeaponModel( "fraggrenade" );
self detach( model, "tag_inhand" );
wait .05;
self startragdoll();
self waittillmatch( "corner_grenade_die", "end" );
}
waitTillGrenadeDrops()
{
self waittill( "grenade_drop_done" );
}
watchEnemyVelocity()
{
self endon( "kill_long_death" );
self endon( "death" );
self.enemyVelocity = ( 0, 0, 0 );
prevenemy = undefined;
prevpos = self.origin;
interval = .15;
while ( 1 )
{
if ( isdefined( self.enemy ) && isdefined( prevenemy ) && self.enemy == prevenemy )
{
curpos = self.enemy.origin;
self.enemyVelocity = vector_multiply( curpos - prevpos, 1 / interval );
prevpos = curpos;
}
else
{
if ( isdefined( self.enemy ) )
prevpos = self.enemy.origin;
else
prevpos = self.origin;
prevenemy = self.enemy;
self.shootEntVelocity = ( 0, 0, 0 );
}
wait interval;
}
}
additive_pain( damage, attacker, direction_vec, point, type, modelName, tagName )
{
self endon( "death" );
if ( !isdefined( self ) )
return;
if ( isdefined( self.doingAdditivePain ) )
return;
if ( damage > self.minPainDamage )
return;
self.doingAdditivePain = true;
painAnimArray = array( %pain_add_standing_belly, %pain_add_standing_left_arm, %pain_add_standing_right_arm );
painAnim = %pain_add_standing_belly;
if ( self damageLocationIsAny( "left_arm_lower", "left_arm_upper", "left_hand" ) )
painAnim = %pain_add_standing_left_arm;
if ( self damageLocationIsAny( "right_arm_lower", "right_arm_upper", "right_hand" ) )
painAnim = %pain_add_standing_right_arm;
else if ( self damageLocationIsAny( "left_leg_upper", "left_leg_lower", "left_foot" ) )
painAnim = %pain_add_standing_left_leg;
else if ( self damageLocationIsAny( "right_leg_upper", "right_leg_lower", "right_foot" ) )
painAnim = %pain_add_standing_right_leg;
else
painAnim = painAnimArray[ randomint( painAnimArray.size ) ];
self setanimlimited( %add_pain, 1, 0.1, 1 );
self setanimlimited( painAnim, 1, 0, 1 );
wait 0.4;
self clearanim( painAnim, 0.2 );
self clearanim( %add_pain, 0.2 );
self.doingAdditivePain = undefined;
}