IW4-Dump-Files/animscripts/combat.gsc

1528 lines
35 KiB
Plaintext
Raw Normal View History

2017-07-08 11:47:21 -07:00
#include animscripts\Utility;
#include animscripts\SetPoseMovement;
#include animscripts\Combat_utility;
#include animscripts\shared;
#include animscripts\animset;
#include common_scripts\Utility;
#using_animtree( "generic_human" );
sqr512 = 512 * 512;
sqr285 = 285 * 285;
sqr100 = 100 * 100;
pistolPullOutDistSq = sqr512 * 0.64;
pistolPutBackDistSq = sqr512;
main()
{
if ( isdefined( self.no_ai ) )
return;
if ( isdefined( self.onSnowMobile ) )
{
animscripts\snowmobile::main();
return;
}
//prof_begin("combat_init");
self endon( "killanimscript" );
[[ self.exception[ "exposed" ] ]]();
animscripts\utility::initialize( "combat" );
self.a.arrivalType = undefined;
if ( isdefined( self.node ) && self.node.type == "Ambush" && self nearNode( self.node ) )
self.ambushNode = self.node;
/#
if ( getdvar( "scr_testgrenadethrows" ) == "on" )
testGrenadeThrowAnimOffsets();
#/
self transitionToCombat();
self do_friendly_fire_reaction();
animscripts\stop::specialIdleLoop();
self setup();
//prof_end("combat_init");
self exposedCombatMainLoop();
self notify( "stop_deciding_how_to_shoot" );
}
end_script()
{
self.ambushNode = undefined;
}
do_friendly_fire_reaction()
{
if ( self.team != "allies" )
return;
if ( self IsMoveSuppressed() && self.prevScript == "move" && self.a.pose == "stand" && !isdefined( self.disableFriendlyFireReaction ) )
{
if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < squared( 128 ) )
return;
self animmode( "zonly_physics" );
self setFlaggedAnimKnobAllRestart( "react", %surprise_stop_v1, %root, 1, 0.2, self.animplaybackrate );
self animscripts\shared::DoNoteTracks( "react" );
}
}
transitionToCombat()
{
if ( isdefined( self.specialIdleAnim ) || isdefined( self.customIdleAnimSet ) )
return;
if ( isdefined( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 512 * 512 )
return;
if ( self.prevScript == "stop" && !self isCQBWalking() && self.a.pose == "stand" )
{
self animmode( "zonly_physics" );
self setFlaggedAnimKnobAllRestart( "transition", %casual_stand_idle_trans_out, %root, 1, 0.2, 1.2 * self.animplaybackrate );
self animscripts\shared::DoNoteTracks( "transition" );
}
}
/#
testGrenadeThrowAnimOffsets()
{
model = getGrenadeModel();
self animmode( "zonly_physics" );
self OrientMode( "face angle", self.angles[ 1 ] );
self.keepClaimedNodeIfValid = true;
foreach ( throwAnim in anim.grenadeThrowAnims )
{
forward = anglestoforward( self.angles );
right = anglestoright( self.angles );
startpos = self.origin;
tag = "TAG_INHAND";
self setFlaggedAnimKnobAllRestart( "grenadetest", throwAnim, %root, 1, 0, 1 );
for ( ;; )
{
self waittill( "grenadetest", notetrack );
if ( notetrack == "grenade_left" || notetrack == "grenade_right" )
self attach( model, tag );
if ( notetrack == "grenade_throw" || notetrack == "grenade throw" )
break;
assert( notetrack != "end" );// we shouldn't hit "end" until after we've hit "grenade_throw"!
if ( notetrack == "end" )// failsafe
break;
}
pos = self getTagOrigin( tag );
baseoffset = pos - startpos;
offset = ( vectordot( baseoffset, forward ), -1 * vectordot( baseoffset, right ), baseoffset[ 2 ] );
// check our answer =)
endpos = startpos + forward * offset[ 0 ] - right * offset[ 1 ] + ( 0, 0, 1 ) * offset[ 2 ];
thread debugLine( startpos, endpos, ( 1, 1, 1 ), 20 );
println( "addGrenadeThrowAnimOffset( %", throwAnim, ", ", offset, " );" );
self detach( model, tag );
wait 1;
}
self.keepClaimedNodeIfValid = false;
}
#/
setup_anim_array()
{
if ( self.a.pose == "stand" )
{
self set_animarray_standing();
}
else if ( self.a.pose == "crouch" )
{
self set_animarray_crouching();
}
else if ( self.a.pose == "prone" )
{
self set_animarray_prone();
}
else
{
assertMsg( "Unsupported self.a.pose: " + self.a.pose );
}
}
// SETUP FUNCTIONS
setup()
{
if ( usingSidearm() && self isStanceAllowed( "stand" ) )
transitionTo( "stand" );
setup_anim_array();
set_aim_and_turn_limits();
self thread stopShortly();
self.previousPitchDelta = 0.0;
self clearAnim( %root, .2 );
setupAim( .2 );
self thread aimIdleThread();
self.a.meleeState = "aim";
self delayStandardMelee();
}
stopShortly()
{
self endon( "killanimscript" );
// we want to stop at about the time we blend out of whatever we were just doing.
wait .2;
self.a.movement = "stop";
}
set_aim_and_turn_limits()
{
//We have a slightly greater pitch reach when stand_exposed
self setDefaultAimLimits();
if ( self.a.pose == "stand" )
{
self.upAimLimit = 60;
self.downAimLimit = -60;
}
self.turnThreshold = self.defaultTurnThreshold;
}
setupExposedCombatLoop()
{
self thread trackShootEntOrPos();
self thread ReacquireWhenNecessary();
self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( "normal" );
self thread watchShootEntVelocity();
self resetGiveUpOnEnemyTime();
if ( isdefined( self.a.magicReloadWhenReachEnemy ) )
{
self animscripts\weaponList::RefillClip();
self.a.magicReloadWhenReachEnemy = undefined;
}
// Hesitate to crouch. Crouching too early can look stupid because we'll tend to stand right back up in a lot of cases.
self.a.dontCrouchTime = gettime() + randomintrange( 500, 1500 );
}
exposedCombatStopUsingRPGCheck( distSqToShootPos )
{
// too close for RPG or out of ammo
if ( usingRocketLauncher() && ( distSqToShootPos < sqr512 || self.a.rockets < 1 ) )
{
if ( self.a.pose != "stand" && self.a.pose != "crouch" )
transitionTo( "crouch" );
if ( self.a.pose == "stand" )
animscripts\shared::throwDownWeapon( %RPG_stand_throw );
else
animscripts\shared::throwDownWeapon( %RPG_crouch_throw );
self clearAnim( %root, 0.2 );
self endFireAndAnimIdleThread();
self setup_anim_array();
self startFireAndAimIdleThread();
return true;
}
return false;
}
exposedCombatCheckStance( distSqToShootPos )
{
if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) )
{
if ( distSqToShootPos < sqr285 )
{
transitionTo( "stand" );
return true;
}
if ( standIfMakesEnemyVisible() )
return true;
}
if ( distSqToShootPos > sqr512 &&
self.a.pose != "crouch" &&
self isStanceAllowed( "crouch" ) &&
!usingSidearm() &&
!isdefined( self.heat ) &&
gettime() >= self.a.dontCrouchTime &&
lengthSquared( self.shootEntVelocity ) < sqr100 )
{
if ( !isdefined( self.shootPos ) || sightTracePassed( self.origin + ( 0, 0, 36 ), self.shootPos, false, undefined ) )
{
transitionTo( "crouch" );
return true;
}
}
return false;
}
exposedCombatCheckReloadOrUsePistol( distSqToShootPos )
{
if ( !usingSidearm() )
{
if ( isdefined( self.forceSideArm ) && self.a.pose == "stand" )
{
if ( self tryUsingSidearm() )
return true;
}
if ( isSniper() && distSqToShootPos < pistolPullOutDistSq )
{
if ( self tryUsingSidearm() )
return true;
}
}
if ( NeedToReload( 0 ) )
{
// TODO: tweak prone exposed reloading to be considered safer
// requiring self.weapon == self.primaryweapon because we dont want him to drop his shotgun and then, if wantshotgun = false, decide to pick up his rifle when he's done
if ( !usingSidearm() && cointoss() && !usingRocketLauncher() && usingPrimary() &&
distSqToShootPos < pistolPullOutDistSq && self isStanceAllowed( "stand" ) )
{
// we need to be standing to switch weapons
if ( self.a.pose != "stand" )
{
transitionTo( "stand" );
return true;
}
if ( self tryUsingSidearm() )
return true;
}
if ( self exposedReload( 0 ) )
return true;
}
return false;
}
exposedCombatCheckPutAwayPistol( distSqToShootPos )
{
if ( usingSidearm() && self.a.pose == "stand" && !isdefined( self.forceSideArm ) )
if ( ( distSqToShootPos > pistolPutBackDistSq ) || ( self.combatMode == "ambush_nodes_only" && ( !isdefined( self.enemy ) || !self cansee( self.enemy ) ) ) )
switchToLastWeapon( %pistol_stand_switch );
}
exposedCombatPositionAdjust()
{
if ( isdefined( self.heat ) && self nearClaimNodeAndAngle() )
{
assert( isdefined( self.node ) );
self safeTeleport( self.nodeoffsetpos, self.node.angles );
}
}
exposedCombatNeedToTurn()
{
if ( needToTurn() )
{
predictTime = 0.25;
if ( isdefined( self.shootEnt ) && !isSentient( self.shootEnt ) )
predictTime = 1.5;
yawToShootEntOrPos = getPredictedAimYawToShootEntOrPos( predictTime );// yaw to where we think our enemy will be in x seconds
if ( TurnToFaceRelativeYaw( yawToShootEntOrPos ) )
return true;
}
return false;
}
exposedCombatMainLoop()
{
self endon( "killanimscript" );
self endon( "combat_restart" );
self setupExposedCombatLoop();
self animMode( "zonly_physics", false );
self OrientMode( "face angle", self.angles[ 1 ] ); // // just face current immediately and rely on turning.
for ( ;; )
{
if ( usingRocketLauncher() )
self.deathFunction = undefined;
self IsInCombat();// reset our in - combat state
// it is important for this to be *after* the set_animarray calls!
if ( WaitForStanceChange() )
continue;
tryMelee();
exposedCombatPositionAdjust();
if ( !isdefined( self.shootPos ) )
{
assert( !isdefined( self.shootEnt ) );
cantSeeEnemyBehavior();
if ( !isdefined( self.enemy ) )
justWaited = true;
continue;
}
// we can use self.shootPos after this point.
assert( isdefined( self.shootPos ) );
self resetGiveUpOnEnemyTime();
distSqToShootPos = lengthsquared( self.origin - self.shootPos );
if ( exposedCombatStopUsingRPGCheck( distSqToShootPos ) )
continue;
if ( exposedCombatNeedToTurn() )
continue;
if ( considerThrowGrenade() )// TODO: make considerThrowGrenade work with shootPos rather than only self.enemy
continue;
if ( exposedCombatCheckReloadOrUsePistol( distSqToShootPos ) )
continue;
if ( usingRocketLauncher() && self.a.pose != "crouch" && randomFloat( 1 ) > 0.65 )
self.deathFunction = ::rpgDeath;
exposedCombatCheckPutAwayPistol( distSqToShootPos );
if ( exposedCombatCheckStance( distSqToShootPos ) )
continue;
if ( aimedAtShootEntOrPos() )
{
self shootUntilNeedToTurn();
self hideFireShowAimIdle();
continue;
}
exposedWait();
}
}
exposedWait()
{
if ( !isdefined( self.enemy ) || !self cansee( self.enemy ) )
{
self endon( "enemy" );
self endon( "shoot_behavior_change" );
wait 0.2 + randomfloat( 0.1 );
self waittill( "do_slow_things" );
}
else
{
wait 0.05;
}
}
standIfMakesEnemyVisible()
{
assert( self.a.pose != "stand" );
assert( self isStanceAllowed( "stand" ) );
if ( isdefined( self.enemy ) && ( !self cansee( self.enemy ) || !self canShootEnemy() ) && sightTracePassed( self.origin + ( 0, 0, 64 ), self.enemy getShootAtPos(), false, undefined ) )
{
self.a.dontCrouchTime = gettime() + 3000;
transitionTo( "stand" );
return true;
}
return false;
}
needToTurn()
{
// Old way, slower
/*
yawToShootEntOrPos = getAimYawToShootEntOrPos(); // yaw to where we think our enemy will be in x seconds
return (abs( yawToShootEntOrPos ) > self.turnThreshold);
*/
// New way
point = self.shootPos;
if ( !isdefined( point ) )
return false;
yaw = self.angles[ 1 ] - VectorToYaw( point - self.origin );
// need to have fudge factor because the gun's origin is different than our origin,
// the closer our distance, the more we need to fudge.
distsq = distanceSquared( self.origin, point );
if ( distsq < 256 * 256 )
{
dist = sqrt( distsq );
if ( dist > 3 )
yaw += asin( -3 / dist );
}
return AbsAngleClamp180( yaw ) > self.turnThreshold;
}
WaitForStanceChange()
{
curstance = self.a.pose;
if ( isdefined( self.a.onback ) )
{
wait 0.1; // something else should correct the stance from "back", or this AI will die on its back, so wait.
return true;
}
if ( curstance == "stand" && isdefined( self.heat ) )
return false;
if ( !self isStanceAllowed( curstance ) )
{
assert( curstance == "stand" || curstance == "crouch" || curstance == "prone" );
otherstance = "crouch";
if ( curstance == "crouch" )
otherstance = "stand";
if ( self isStanceAllowed( otherstance ) )
{
if ( curstance == "stand" && usingSidearm() )
return false;
transitionTo( otherstance );
return true;
}
}
return false;
}
cantSeeEnemyBehavior()
{
//prof_begin("combat_cantSeeEnemyBehavior");
if ( self.a.pose != "stand" && self isStanceAllowed( "stand" ) && standIfMakesEnemyVisible() )
return true;
time = gettime();
self.a.dontCrouchTime = time + 1500;
if ( isdefined( self.group ) && isdefined( self.group.forward ) )
{
relYaw = AngleClamp180( self.angles[ 1 ] - vectorToYaw( self.group.forward ) );
if ( self TurnToFaceRelativeYaw( relYaw ) )
return true;
}
if ( isdefined( self.node ) && isdefined( anim.isCombatScriptNode[ self.node.type ] ) )
{
relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] );
if ( self TurnToFaceRelativeYaw( relYaw ) )
return true;
}
else if ( (isdefined( self.enemy ) && self seeRecently( self.enemy, 2 )) || time > self.a.scriptStartTime + 1200 )
{
relYaw = undefined;
likelyEnemyDir = self getAnglesToLikelyEnemyPath();
if ( isdefined( likelyEnemyDir ) )
{
relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] );
}
else if ( isdefined( self.node ) )
{
relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] );
}
else if ( isdefined( self.enemy ) )
{
likelyEnemyDir = vectorToAngles( self lastKnownPos( self.enemy ) - self.origin );
relYaw = AngleClamp180( self.angles[ 1 ] - likelyEnemyDir[ 1 ] );
}
if ( isdefined( relYaw ) && self TurnToFaceRelativeYaw( relYaw ) )
return true;
}
// the above likely enemy path is more important than node angles
else if ( isdefined( self.heat ) && self nearClaimNode() )
{
relYaw = AngleClamp180( self.angles[ 1 ] - self.node.angles[ 1 ] );
if ( self TurnToFaceRelativeYaw( relYaw ) )
return true;
}
if ( considerThrowGrenade() )
return true;
givenUpOnEnemy = ( self.a.nextGiveUpOnEnemyTime < time );
threshold = 0;
if ( givenUpOnEnemy )
threshold = 0.99999;
if ( self exposedReload( threshold ) )
return true;
if ( givenUpOnEnemy && usingSidearm() )
{
// switch back to main weapon so we can reload it too before another enemy appears
switchToLastWeapon( %pistol_stand_switch );
return true;
}
/*if ( shouldSwapShotgun() )
{
self swapShotgun();
return true;
}*/
cantSeeEnemyWait();
return true;
}
cantSeeEnemyWait()
{
self endon( "shoot_behavior_change" );
wait 0.4 + randomfloat( 0.4 );
self waittill( "do_slow_things" );
}
resetGiveUpOnEnemyTime()
{
self.a.nextGiveUpOnEnemyTime = gettime() + randomintrange( 2000, 4000 );
}
TurnToFaceRelativeYaw( faceYaw )
{
if ( faceYaw < 0 - self.turnThreshold )
{
if ( self.a.pose == "prone" )
{
self animscripts\cover_prone::proneTo( "crouch" );
self set_animarray_crouching();
}
self doTurn( "left", 0 - faceYaw );
self maps\_gameskill::didSomethingOtherThanShooting();
return true;
}
if ( faceYaw > self.turnThreshold )
{
if ( self.a.pose == "prone" )
{
self animscripts\cover_prone::proneTo( "crouch" );
self set_animarray_crouching();
}
self doTurn( "right", faceYaw );
self maps\_gameskill::didSomethingOtherThanShooting();
return true;
}
return false;
}
watchShootEntVelocity()
{
self endon( "killanimscript" );
self.shootEntVelocity = ( 0, 0, 0 );
prevshootent = undefined;
prevpos = self.origin;
interval = .15;
while ( 1 )
{
if ( isdefined( self.shootEnt ) && isdefined( prevshootent ) && self.shootEnt == prevshootent )
{
curpos = self.shootEnt.origin;
self.shootEntVelocity = vector_multiply( curpos - prevpos, 1 / interval );
prevpos = curpos;
}
else
{
if ( isdefined( self.shootEnt ) )
prevpos = self.shootEnt.origin;
else
prevpos = self.origin;
prevshootent = self.shootEnt;
self.shootEntVelocity = ( 0, 0, 0 );
}
wait interval;
}
}
shouldSwapShotgun()
{
return false;// anims aren't set up yet
/*
if ( self.a.pose != "stand" )
return false;
if ( self usingSidearm() )
return false;
usingShotgun = isShotgun( self.primaryweapon );
wantShotgun = isdefined( self.wantShotgun ) && self.wantShotgun );
if ( wantShotgun == usingShotgun )
return false;
if ( !wantShotgun ) // there is no standing shotgun putaway animation
return false;
return true;*/
}
/*swapShotgun()
{
assert( self shouldSwapShotgun() );
assert( isdefined( self.wantShotgun ) );
if ( self.wantShotgun )
{
self setFlaggedAnimKnobAllRestart( "weapon_swap", %shotgun_stand_pullout, %body, 1, .2, 1 );
self thread DoNoteTracksWithEndon( "weapon_swap" );
self waittill( "weapon_swap", "" );
self waittill( "weapon_swap", "end" );
}
else
{
assert(false); // we don't have a standing shotgun putaway animation
}
}*/
DoNoteTracksWithEndon( animname )
{
self endon( "killanimscript" );
self animscripts\shared::DoNoteTracks( animname );
}
// does turntable movement to face the enemy;
// should be used sparingly because turn animations look better.
faceEnemyImmediately()
{
self endon( "killanimscript" );
self notify( "facing_enemy_immediately" );
self endon( "facing_enemy_immediately" );
maxYawChange = 5;// degrees per frame
while ( 1 )
{
yawChange = 0 - GetYawToEnemy();
if ( abs( yawChange ) < 2 )
break;
if ( abs( yawChange ) > maxYawChange )
yawChange = maxYawChange * sign( yawChange );
self OrientMode( "face angle", self.angles[ 1 ] + yawChange );
wait .05;
}
self OrientMode( "face current" );
self notify( "can_stop_turning" );
}
isDeltaAllowed( theanim )
{
delta = getMoveDelta( theanim, 0, 1 );
endPoint = self localToWorldCoords( delta );
return self isInGoal( endPoint ) && self mayMoveToPoint( endPoint );
}
isAnimDeltaInGoal( theanim )
{
delta = getMoveDelta( theanim, 0, 1 );
endPoint = self localToWorldCoords( delta );
return self isInGoal( endPoint );
}
doTurn( direction, amount )
{
knowWhereToShoot = isdefined( self.shootPos );
rate = 1;
transTime = 0.2;
mustFaceEnemy = ( isdefined( self.enemy ) && !isdefined( self.turnToMatchNode ) && self seeRecently( self.enemy, 2 ) && distanceSquared( self.enemy.origin, self.origin ) < sqr512 );
if ( self.a.scriptStartTime + 500 > gettime() )
{
transTime = 0.25;// if it's the first thing we're doing, always blend slowly
if ( mustFaceEnemy )
self thread faceEnemyImmediately();
}
else if ( mustFaceEnemy )
{
urgency = 1.0 - ( distance( self.enemy.origin, self.origin ) / 512 );
rate = 1 + urgency * 1;
// ( ensure transTime <= 0.2 / rate )
if ( rate > 2 )
transTime = .05;
else if ( rate > 1.3 )
transTime = .1;
else
transTime = .15;
}
angle = 0;
if ( amount > 157.5 )
angle = 180;
else if ( amount > 112.5 )
angle = 135;
else if ( amount > 67.5 )
angle = 90;
else
angle = 45;
animname = "turn_" + direction + "_" + angle;
turnanim = animarray( animname );
if ( isdefined( self.turnToMatchNode ) )
self animmode( "angle deltas", false );
else if ( isdefined( self.node ) && isdefined( anim.isCombatPathNode[ self.node.type ] ) && distanceSquared( self.origin, self.node.origin ) < 16 * 16 )
self animmode( "angle deltas", false );
else if ( isAnimDeltaInGoal( turnanim ) )
self animMode( "zonly_physics", false );
else
self animmode( "angle deltas", false );
self setAnimKnobAll( %exposed_aiming, %body, 1, transTime );
if ( !isdefined( self.turnToMatchNode ) )
self TurningAimingOn( transTime );
self setAnimLimited( %turn, 1, transTime );
if ( isdefined( self.heat ) )
rate = min( 1.0, rate ); // TEMP 1.0, adjust animations first
else if ( isdefined( self.turnToMatchNode ) )
rate = max( 1.5, rate );
self setFlaggedAnimKnobLimitedRestart( "turn", turnanim, 1, transTime, rate );
self notify( "turning" );
if ( knowWhereToShoot && !isdefined( self.turnToMatchNode ) && !isdefined( self.heat ) )
self thread shootWhileTurning();
doTurnNotetracks();
self setanimlimited( %turn, 0, .2 );
if ( !isdefined( self.turnToMatchNode ) )
self TurningAimingOff( .2 );
if ( !isdefined( self.turnToMatchNode ) )
{
self clearanim( %turn, .2 );
self setanimknob( %exposed_aiming, 1, .2, 1 );
}
else
{
self clearanim( %exposed_modern, .3 );
}
// if we didn't actually turn, code prevented us from doing so.
// give up and turntable.
if ( isdefined( self.turnLastResort ) )
{
self.turnLastResort = undefined;
self thread faceEnemyImmediately();
}
self animMode( "zonly_physics", false );
self notify( "done turning" );
}
doTurnNotetracks()
{
//self endon( "turning_isnt_working" );
self endon( "can_stop_turning" );
//self thread makeSureTurnWorks();
self animscripts\shared::DoNoteTracks( "turn" );
}
makeSureTurnWorks()
{
self endon( "killanimscript" );
self endon( "done turning" );
startAngle = self.angles[ 1 ];
wait .3;
if ( self.angles[ 1 ] == startAngle )
{
self notify( "turning_isnt_working" );
self.turnLastResort = true;
}
}
TurningAimingOn( transTime )
{
self setAnimLimited( animarray( "straight_level" ), 0, transTime );
self setAnim( %add_idle, 0, transTime );
if ( !weapon_pump_action_shotgun() )
self clearAnim( %add_fire, .2 );
}
TurningAimingOff( transTime )
{
self setAnimLimited( animarray( "straight_level" ), 1, transTime );
self setAnim( %add_idle, 1, transTime );
}
shootWhileTurning()
{
self endon( "killanimscript" );
self endon( "done turning" );
if ( usingRocketLauncher() )
return;
shootUntilShootBehaviorChange();
self clearAnim( %add_fire, .2 );
}
shootUntilNeedToTurn()
{
self thread watchForNeedToTurnOrTimeout();
self endon( "need_to_turn" );
self thread keepTryingToMelee();
shootUntilShootBehaviorChange();
self notify( "stop_watching_for_need_to_turn" );
self notify( "stop_trying_to_melee" );
}
watchForNeedToTurnOrTimeout()
{
self endon( "killanimscript" );
self endon( "stop_watching_for_need_to_turn" );
endtime = gettime() + 4000 + randomint( 2000 );
while ( 1 )
{
if ( gettime() > endtime || needToTurn() )
{
self notify( "need_to_turn" );
break;
}
wait .1;
}
}
considerThrowGrenade()
{
if ( !myGrenadeCoolDownElapsed() )// early out for efficiency
return false;
if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level.player ) )
{
if ( tryExposedThrowGrenade( level.player, 200 ) )
return true;
}
if ( isdefined( self.enemy ) && tryExposedThrowGrenade( self.enemy, self.minExposedGrenadeDist ) )
return true;
self.a.nextGrenadeTryTime = gettime() + 500;// don't try this too often
return false;
}
tryExposedThrowGrenade( throwAt, minDist )
{
threw = false;
if ( isdefined( self.dontEverShoot ) || isdefined( throwAt.dontAttackMe ) )
return false;
if ( !isdefined( self.a.array[ "exposed_grenade" ] ) )
return false;
throwSpot = throwAt.origin;
if ( !self canSee( throwAt ) )
{
if ( isdefined( self.enemy ) && throwAt == self.enemy && isdefined( self.shootPos ) )
throwSpot = self.shootPos;
}
if ( !self canSee( throwAt ) )
minDist = 100;
if ( distanceSquared( self.origin, throwSpot ) > minDist * minDist && self.a.pose == self.a.grenadeThrowPose )
{
self setActiveGrenadeTimer( throwAt );
if ( !grenadeCoolDownElapsed( throwAt ) )
return false;
yaw = GetYawToSpot( throwSpot );
if ( abs( yaw ) < 60 )
{
throwAnims = [];
foreach( throwAnim in ( self.a.array[ "exposed_grenade" ] ) )
{
if ( isDeltaAllowed( throwAnim ) )
throwAnims[ throwAnims.size ] = throwAnim;
}
if ( throwAnims.size > 0 )
{
self setanim( %exposed_aiming, 0, .1 );
self animMode( "zonly_physics" );
setAnimAimWeight( 0, 0 );
threw = TryGrenade( throwAt, throwAnims[ randomint( throwAnims.size ) ] );
self setanim( %exposed_aiming, 1, .1 );
if ( threw )
setAnimAimWeight( 1, .5 );// ease into aiming
else
setAnimAimWeight( 1, 0 );
}
}
}
if ( threw )
self maps\_gameskill::didSomethingOtherThanShooting();
return threw;
}
transitionTo( newPose )
{
if ( newPose == self.a.pose )
return;
// allow using pistol but it might look bad, put a new animation if so
// assert( !usingSidearm() );
transAnimName = self.a.pose + "_2_" + newPose;
if ( !isdefined( self.a.array ) )
return;
transAnim = self.a.array[ transAnimName ];
if ( !isdefined( transAnim ) )
return;
self clearanim( %root, .3 );
self endFireAndAnimIdleThread();
if ( newPose == "stand" )
rate = 2;// gotta stand up fast!
else
rate = 1.5;
if ( !animHasNoteTrack( transAnim, "anim_pose = \"" + newPose + "\"" ) )
{
println( "error: " + self.a.pose + "_2_" + newPose + " missing notetrack to set pose!" );
}
self setFlaggedAnimKnobAllRestart( "trans", transanim, %body, 1, .2, rate );
transTime = getAnimLength( transanim ) / rate;
playTime = transTime - 0.3;
if ( playTime < 0.2 )
playTime = 0.2;
self animscripts\shared::DoNoteTracksForTime( playTime, "trans" );
self.a.pose = newPose;
setup_anim_array();
self startFireAndAimIdleThread();
self maps\_gameskill::didSomethingOtherThanShooting();
}
keepTryingToMelee()
{
self endon( "killanimscript" );
self endon( "stop_trying_to_melee" );
self endon( "done turning" );
self endon( "need_to_turn" );
self endon( "shoot_behavior_change" );
while ( 1 )
{
wait .2 + randomfloat( .3 );
// this function is running when we're doing something like shooting or reloading.
// we only want to melee if we would look really stupid by continuing to do what we're trying to get done.
// only melee if our enemy is very close.
if ( isdefined( self.enemy ) )
{
if ( isPlayer( self.enemy ) )
checkDistSq = 200 * 200;
else
checkDistSq = 100 * 100;
if ( distanceSquared( self.enemy.origin, self.origin ) < checkDistSq )
tryMelee();
}
}
}
tryMelee()
{
animscripts\melee::Melee_TryExecuting(); // will start a new anim script and stop combat when successful
}
delayStandardMelee()
{
if ( isdefined( self.noMeleeChargeDelay ) )
return;
if ( isPlayer( self.enemy ) )
return;
// give the AI a chance to charge the player if he forced him out of cover
//if ( isPlayer( self.enemy ) && isDefined( self.meleeCoverChargeGraceEndTime ) && (self.meleeCoverChargeGraceEndTime > getTime()) )
// return;
animscripts\melee::Melee_Standard_DelayStandardCharge( self.enemy );
}
exposedReload( threshold )
{
if ( NeedToReload( threshold ) )
{
self.a.exposedReloading = true;
self endFireAndAnimIdleThread();
reloadAnim = undefined;
if ( isdefined( self.specialReloadAnimFunc ) )
{
reloadAnim = self [[ self.specialReloadAnimFunc ]]();
self.keepClaimedNode = true;
}
else
{
reloadAnim = animArrayPickRandom( "reload" );
if ( self.a.pose == "stand" && animArrayAnyExist( "reload_crouchhide" ) && cointoss() )
reloadAnim = animArrayPickRandom( "reload_crouchhide" );
}
self thread keepTryingToMelee();
self.finishedReload = false;
// pistol reload looks weird pointing at fixed current angle
if ( weaponClass( self.weapon ) == "pistol" )
self orientmode( "face default" );
self doReloadAnim( reloadAnim, threshold > .05 );// this will return at the time when we should start aiming
self notify( "abort_reload" );// make sure threads that doReloadAnim() started finish
self orientmode( "face current" );
if ( self.finishedReload )
self animscripts\weaponList::RefillClip();
self clearanim( %reload, .2 );
self.keepClaimedNode = false;
self notify( "stop_trying_to_melee" );
self.a.exposedReloading = false;
self maps\_gameskill::didSomethingOtherThanShooting();
self startFireAndAimIdleThread();
return true;
}
return false;
}
doReloadAnim( reloadAnim, stopWhenCanShoot )
{
self endon( "abort_reload" );
if ( stopWhenCanShoot )
self thread abortReloadWhenCanShoot();
animRate = 1;
if ( !self usingSidearm() && !isShotgun( self.weapon ) && isdefined( self.enemy ) && self canSee( self.enemy ) && distanceSquared( self.enemy.origin, self.origin ) < 1024*1024 )
animRate = 1.2;
flagName = "reload_" + getUniqueFlagNameIndex();
self clearanim( %root, 0.2 );
self setflaggedanimrestart( flagName, reloadAnim, 1, .2, animRate );
self thread notifyOnStartAim( "abort_reload", flagName );
self endon( "start_aim" );
self animscripts\shared::DoNoteTracks( flagName );
self.finishedReload = true;
}
abortReloadWhenCanShoot()
{
self endon( "abort_reload" );
self endon( "killanimscript" );
while ( 1 )
{
if ( isdefined( self.shootEnt ) && self canSee( self.shootEnt ) )
break;
wait .05;
}
self notify( "abort_reload" );
}
notifyOnStartAim( endonStr, flagName )
{
self endon( endonStr );
self waittillmatch( flagName, "start_aim" );
self.finishedReload = true;
self notify( "start_aim" );
}
finishNoteTracks( animname )
{
self endon( "killanimscript" );
animscripts\shared::DoNoteTracks( animname );
}
drop_turret()
{
maps\_mgturret::dropTurret();
// level.theturret = turret;
// throwVel = 75 + randomInt(50);
// self animscripts\shared::PutGunInHand("right");
self animscripts\weaponList::RefillClip();
self.a.needsToRechamber = 0;
self notify( "dropped_gun" );
maps\_mgturret::restoreDefaults();
}
exception_exposed_mg42_portable()
{
drop_turret();
}
tryUsingSidearm()
{
// temp fix! we run out of bones on a particular friendly model with a shotgun who tries to pull out his pistol.
if ( isdefined( self.secondaryWeapon ) && isShotgun( self.secondaryweapon ) )
return false;
if ( isdefined( self.no_pistol_switch ) )
return false;
// TEMP no pistol crouch pullout yet
self.a.pose = "stand";
switchToSidearm( %pistol_stand_pullout );
return true;
}
switchToSidearm( swapAnim )
{
self endon( "killanimscript" );
assert( self.sidearm != "" );
self thread putGunBackInHandOnKillAnimScript();
self endFireAndAnimIdleThread();
self.swapAnim = swapAnim;
self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .2, fasterAnimSpeed() );
self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePickup, "end_weapon_swap" );
self clearAnim( self.swapAnim, 0.2 );
self notify( "facing_enemy_immediately" );
// notify for level script
self notify( "switched_to_sidearm" );
self maps\_gameskill::didSomethingOtherThanShooting();
}
DoNoteTracksPostCallbackWithEndon( flagName, interceptFunction, endonMsg )
{
self endon( endonMsg );
self animscripts\shared::DoNoteTracksPostCallback( flagName, interceptFunction );
}
faceEnemyDelay( delay )
{
self endon( "killanimscript" );
wait delay;
self faceEnemyImmediately();
}
handlePickup( notetrack )
{
if ( notetrack == "pistol_pickup" )
{
self clearAnim( animarray( "straight_level" ), 0 );
self set_animarray_standing();
self thread faceEnemyDelay( 0.25 );
}
else if ( notetrack == "start_aim" )
{
startFireAndAimIdleThread();
if ( self needToTurn() )
self notify( "end_weapon_swap" );
}
}
// %pistol_stand_switch
switchToLastWeapon( swapAnim, cleanUp )
{
self endon( "killanimscript" );
assertex( self.lastWeapon != getAISidearmWeapon() || self.lastWeapon == "none", "AI \"" + self.classname + "\" using sidearm trying to switch back to sidearm. lastweapon = \"" + self.lastWeapon + "\", primaryweapon = \"" + self.primaryweapon + "\"" );
assertex( self.lastWeapon == getAIPrimaryWeapon() || self.lastWeapon == getAISecondaryWeapon() );
self endFireAndAnimIdleThread();
self.swapAnim = swapAnim;
self setFlaggedAnimKnobAllRestart( "weapon swap", swapAnim, %body, 1, .1, 1 );
if ( isdefined( cleanUp ) )
self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handleCleanUpPutaway, "end_weapon_swap" );
else
self DoNoteTracksPostCallbackWithEndon( "weapon swap", ::handlePutaway, "end_weapon_swap" );
self clearanim( self.swapAnim, 0.2 );
self notify( "switched_to_lastweapon" );
self maps\_gameskill::didSomethingOtherThanShooting();
}
handlePutaway( notetrack )
{
if ( notetrack == "pistol_putaway" )
{
self clearAnim( animarray( "straight_level" ), 0 );
self set_animarray_standing();
self thread putGunBackInHandOnKillAnimScript();
}
else if ( notetrack == "start_aim" )
{
startFireAndAimIdleThread();
if ( self needToTurn() )
self notify( "end_weapon_swap" );
}
}
handleCleanUpPutaway( notetrack )
{
if ( notetrack == "pistol_putaway" )
self thread putGunBackInHandOnKillAnimScript();
else if ( issubstr( notetrack, "anim_gunhand" ) )
self notify( "end_weapon_swap" );
}
rpgDeath()
{
if ( !usingRocketLauncher() || self.bulletsInClip == 0 )
return false;
if ( randomFloat( 1 ) > 0.5 )
self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death, %root, 1, .05, 1 );
else
self SetFlaggedAnimKnobAll( "deathanim", %RPG_stand_death_stagger, %root, 1, .05, 1 );
self animscripts\shared::DoNoteTracks( "deathanim" );
self animscripts\shared::DropAllAIWeapons();
return;
}
ReacquireWhenNecessary()
{
self endon( "killanimscript" );
self.a.exposedReloading = false;
while ( 1 )
{
wait .2;
if ( isdefined( self.enemy ) && !self seeRecently( self.enemy, 2 ) )
{
if ( self.combatMode == "ambush" || self.combatMode == "ambush_nodes_only" )
continue;
}
TryExposedReacquire();
}
}
// this function is meant to be called many times in succession.
// each time it tries another option, until eventually it finds something it can do.
TryExposedReacquire()
{
if ( self.fixedNode )
return;
//prof_begin( "TryExposedReacquire" );
if ( !isdefined( self.enemy ) )
{
self.reacquire_state = 0;
return;
}
// don't do reacquire move when temporarily blocked by teammate
if ( gettime() < self.teamMoveWaitTime )
{
self.reacquire_state = 0;
return;
}
if ( isdefined( self.prevEnemy ) && self.prevEnemy != self.enemy )
{
self.reacquire_state = 0;
self.prevEnemy = undefined;
return;
}
self.prevEnemy = self.enemy;
if ( self canSee( self.enemy ) && self canShootEnemy() )
{
self.reacquire_state = 0;
return;
}
if ( isdefined( self.finishedReload ) && !self.finishedReload )
{
self.reacquire_state = 0;
return;
}
// don't do reacquire unless facing enemy
dirToEnemy = vectornormalize( self.enemy.origin - self.origin );
forward = anglesToForward( self.angles );
if ( vectordot( dirToEnemy, forward ) < 0.5 ) // (0.5 = cos60)
{
self.reacquire_state = 0;
return;
}
if ( self.a.exposedReloading && NeedToReload( .25 ) && self.enemy.health > self.enemy.maxhealth * .5 )
{
self.reacquire_state = 0;
return;
}
if ( shouldHelpAdvancingTeammate() && self.reacquire_state < 3 )
self.reacquire_state = 3;
switch( self.reacquire_state )
{
case 0:
if ( self ReacquireStep( 32 ) )
return;
break;
case 1:
if ( self ReacquireStep( 64 ) )
{
self.reacquire_state = 0;
return;
}
break;
case 2:
if ( self ReacquireStep( 96 ) )
{
self.reacquire_state = 0;
return;
}
break;
case 3:
if ( tryRunningToEnemy( false ) )
{
self.reacquire_state = 0;
return;
}
break;
case 4:
if ( !( self canSee( self.enemy ) ) || !( self canShootEnemy() ) )
self FlagEnemyUnattackable();
break;
default:
// don't do anything for a while
if ( self.reacquire_state > 15 )
{
self.reacquire_state = 0;
return;
}
break;
}
self.reacquire_state++;
}