IW4-Dump-Files/animscripts/cover_behavior.gsc

972 lines
23 KiB
Plaintext

#include maps\_utility;
#include animscripts\combat_utility;
#include animscripts\utility;
#include animscripts\shared;
#include common_scripts\utility;
/*
This file contains the overall behavior for all "whack-a-mole" cover nodes.
Callbacks which must be defined:
All callbacks should return true or false depending on whether they succeeded in doing something.
If functionality for a callback isn't available, just don't define it.
mainLoopStart()
optional
reload()
plays a reload animation in a hidden position
leaveCoverAndShoot()
does the main attacking; steps out or stands up and fires, goes back to hiding.
should obey orders from decideWhatAndHowToShoot in shoot_behavior.gsc.
look( maxtime )
looks for up to maxtime, stopping and returning if enemy becomes visible or if suppressed
fastlook()
looks quickly
idle()
idles until the "end_idle" notify.
flinch()
flinches briefly (1-2 seconds), doesn't need to return true or false.
grenade( throwAt )
steps out and throws a grenade at the given player / ai
grenadehidden( throwAt )
throws a grenade at the given player / ai without leaving cover
blindfire()
blindfires from cover
example:
behaviorCallbacks = spawnstruct();
behaviorCallbacks.reload = ::reload;
...
animscripts\cover_behavior::main( behaviorCallbacks );
*/
#using_animtree( "generic_human" );
MELEE_GRACE_PERIOD_REQUIRED_TIME = 3000;
MELEE_GRACE_PERIOD_GIVEN_TIME = 5000;
main( behaviorCallbacks )
{
self.couldntSeeEnemyPos = self.origin;// ( set couldntSeeEnemyPos to a place the enemy can't be while we're in corner behavior )
behaviorStartTime = gettime();
coverTimers = spawnstruct();
coverTimers.nextAllowedLookTime = behaviorStartTime - 1;
coverTimers.nextAllowedSuppressTime = behaviorStartTime - 1;
// we won't look for better cover purely out of boredom until this time
resetLookForBetterCoverTime();
resetRespondToDeathTime();
self.seekOutEnemyTime = gettime();
self.a.lastEncounterTime = behaviorStartTime;
self.a.idlingAtCover = false;
self.a.movement = "stop";
// if we break out of cover mode after this time, we will get a grace period during which we can melee charge the player
self.meleeCoverChargeMinTime = behaviorStartTime + MELEE_GRACE_PERIOD_REQUIRED_TIME;
/#
if ( getdvar( "scr_coveridle" ) == "1" )
self.coverNode.script_onlyidle = true;
#/
self thread watchSuppression();
desynched = ( gettime() > 2500 );
correctAngles = getCorrectCoverAngles();
for ( ;; )
{
if ( shouldHelpAdvancingTeammate() )
{
if ( tryRunningToEnemy( true ) )
{
wait 0.05;
continue;
}
}
if ( isdefined( behaviorCallbacks.mainLoopStart ) )
{
startTime = gettime();
self thread endIdleAtFrameEnd();
[[ behaviorCallbacks.mainLoopStart ]]();
if ( gettime() == startTime )
self notify( "dont_end_idle" );
}
if ( isdefined( behaviorCallbacks.moveToNearByCover ) )
{
if ( [[ behaviorCallbacks.moveToNearByCover ]]() )
continue;
}
self safeTeleport( self.covernode.origin, correctAngles );
if ( !desynched )
{
idle( behaviorCallbacks, 0.05 + randomfloat( 1.5 ) );
desynched = true;
continue;
}
if ( doNonAttackCoverBehavior( behaviorCallbacks ) )
continue;
if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level.player ) )
{
if ( tryThrowingGrenade( behaviorCallbacks, level.player ) )
continue;
}
if ( respondToDeadTeammate() )
return;
// determine visibility and suppressability of enemy.
visibleEnemy = false;
suppressableEnemy = false;
if ( isalive( self.enemy ) )
{
visibleEnemy = isEnemyVisibleFromExposed();
suppressableEnemy = canSuppressEnemyFromExposed();
}
// decide what to do.
if ( visibleEnemy )
{
if ( self.a.getBoredOfThisNodeTime < gettime() )
{
if ( lookForBetterCover() )
return;
}
attackVisibleEnemy( behaviorCallbacks );
}
else
{
if ( isdefined( self.aggressiveMode ) || enemyIsHiding() )
{
if ( advanceOnHidingEnemy() )
return;
}
if ( suppressableEnemy )
{
attackSuppressableEnemy( behaviorCallbacks, coverTimers );
}
else
{
if ( attackNothingToDo( behaviorCallbacks, coverTimers ) )
return;
}
}
}
}
end_script( coverMode )
{
self.turnToMatchNode = undefined;
self.a.prevAttack = undefined;
if ( isDefined( self.meleeCoverChargeMinTime ) && (self.meleeCoverChargeMinTime <= getTime()) )
{
// give the AI a chance to charge the player if he forced him out of cover
self.meleeCoverChargeGraceEndTime = getTime() + MELEE_GRACE_PERIOD_GIVEN_TIME;
self.meleeCoverChargeMinTime = undefined;
}
}
getCorrectCoverAngles()
{
correctAngles = ( self.coverNode.angles[ 0 ], getNodeForwardYaw( self.coverNode ), self.coverNode.angles[ 2 ] );
return correctAngles;
}
RESPOND_TO_DEATH_RETRY_INTERVAL = 30 * 1000;
respondToDeadTeammate()
{
if ( self atDangerousNode() && self.a.respondToDeathTime < gettime() )
{
if ( lookForBetterCover() )
return true;
self.a.respondToDeathTime = gettime() + RESPOND_TO_DEATH_RETRY_INTERVAL;
}
return false;
}
doNonAttackCoverBehavior( behaviorCallbacks )
{
/#
if ( isDefined( self.coverNode.script_onlyidle ) )
{
assert( self.coverNode.script_onlyidle );// true or undefined
idle( behaviorCallbacks );
return true;
}
#/
// if we're suppressed, we do other things.
if ( suppressedBehavior( behaviorCallbacks ) )
{
if ( isEnemyVisibleFromExposed() )
resetSeekOutEnemyTime();
self.a.lastEncounterTime = gettime();
return true;
}
// reload if we need to; everything in this loop involves shooting.
if ( coverReload( behaviorCallbacks, 0 ) )
return true;
return false;
}
attackVisibleEnemy( behaviorCallbacks )
{
if ( distanceSquared( self.origin, self.enemy.origin ) > 750 * 750 )
{
if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
return;
}
if ( leaveCoverAndShoot( behaviorCallbacks, "normal" ) )
{
resetSeekOutEnemyTime();
self.a.lastEncounterTime = gettime();
}
else
{
idle( behaviorCallbacks );
}
}
attackSuppressableEnemy( behaviorCallbacks, coverTimers )
{
if ( self.doingAmbush )
{
if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) )
return;
}
else if ( self.provideCoveringFire || gettime() >= coverTimers.nextAllowedSuppressTime )
{
preferredActivity = "suppress";
if ( !self.provideCoveringFire && ( gettime() - self.lastSuppressionTime ) > 5000 && randomint( 3 ) < 2 )
preferredActivity = "ambush";
else if ( !self animscripts\shoot_behavior::shouldSuppress() )
preferredActivity = "ambush";
if ( leaveCoverAndShoot( behaviorCallbacks, preferredActivity ) )
{
coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 3000, 20000 );
// if they're there, we've seen them
if ( isEnemyVisibleFromExposed() )
self.a.lastEncounterTime = gettime();
return;
}
}
if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
return;
idle( behaviorCallbacks );
}
attackNothingToDo( behaviorCallbacks, coverTimers )
{
if ( coverReload( behaviorCallbacks, 0.1 ) )
return false;
if ( isdefined( self.enemy ) )
{
if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
return false;
}
if ( !self.doingAmbush && gettime() >= coverTimers.nextAllowedLookTime )
{
if ( lookForEnemy( behaviorCallbacks ) )
{
coverTimers.nextAllowedLookTime = gettime() + randomintrange( 4000, 15000 );
// if they're there, we've seen them
return false;
}
}
// we're *really* bored right now
if ( gettime() > self.a.getBoredOfThisNodeTime )
{
if ( cantFindAnythingToDo() )
return true;
}
if ( self.doingAmbush || ( gettime() >= coverTimers.nextAllowedSuppressTime && isdefined( self.enemy ) ) )
{
// be ready to ambush them if they happen to show up
if ( leaveCoverAndShoot( behaviorCallbacks, "ambush" ) )
{
if ( isEnemyVisibleFromExposed() )
resetSeekOutEnemyTime();
self.a.lastEncounterTime = gettime();
coverTimers.nextAllowedSuppressTime = gettime() + randomintrange( 6000, 20000 );
return false;
}
}
idle( behaviorCallbacks );
return false;
}
isEnemyVisibleFromExposed()
{
if ( !isdefined( self.enemy ) )
return false;
// if we couldn't see our enemy last time we stepped out, and they haven't moved, assume we still can't see them.
if ( distanceSquared( self.enemy.origin, self.couldntSeeEnemyPos ) < 16 * 16 )
return false;
else
return canSeeEnemyFromExposed();
}
suppressedBehavior( behaviorCallbacks )
{
if ( !isSuppressedWrapper() )
return false;
nextAllowedBlindfireTime = gettime();
justlooked = true;
//prof_begin( "suppressedBehavior" );
while ( isSuppressedWrapper() )
{
justlooked = false;
self safeTeleport( self.coverNode.origin );
tryMovingNodes = true;
// guys that favor blindfire should try to blindfire instead of move a lot more
if ( isdefined( self.favor_blindfire ) )
tryMovingNodes = coinToss();
if ( tryMovingNodes )
{
if ( tryToGetOutOfDangerousSituation( behaviorCallbacks ) )
{
self notify( "killanimscript" );
//prof_end( "suppressedBehavior" );
return true;
}
}
// if we're only at a concealment node, and it's not providing cover, we shouldn't try to use the cover to keep us safe!
if ( self.a.atConcealmentNode && self canSeeEnemy() )
{
//prof_end( "suppressedBehavior" );
return false;
}
if ( isEnemyVisibleFromExposed() || canSuppressEnemyFromExposed() )
{
if ( isdefined( anim.throwGrenadeAtPlayerASAP ) && isAlive( level.player ) )
{
if ( tryThrowingGrenade( behaviorCallbacks, level.player ) )
continue;
}
if ( coverReload( behaviorCallbacks, 0 ) )
continue;
if ( self.team != "allies" && gettime() >= nextAllowedBlindfireTime )
{
if ( blindfire( behaviorCallbacks ) )
{
nextAllowedBlindfireTime = gettime();
if ( !isdefined( self.favor_blindfire ) )
nextAllowedBlindfireTime += randomintrange( 3000, 12000 );
continue;
}
}
if ( tryThrowingGrenade( behaviorCallbacks, self.enemy ) )
{
justlooked = true;
continue;
}
}
if ( coverReload( behaviorCallbacks, 0.1 ) )
continue;
//prof_end( "suppressedBehavior" );
idle( behaviorCallbacks );
}
if ( !justlooked && randomint( 2 ) == 0 )
lookfast( behaviorCallbacks );
//prof_end( "suppressedBehavior" );
return true;
}
// returns array of integers 0 through n-1, in random order
getPermutation( n )
{
permutation = [];
assert( n > 0 );
if ( n == 1 )
{
permutation[ 0 ] = 0;
}
else if ( n == 2 )
{
permutation[ 0 ] = randomint( 2 );
permutation[ 1 ] = 1 - permutation[ 0 ];
}
else
{
for ( i = 0; i < n; i++ )
permutation[ i ] = i;
for ( i = 0; i < n; i++ )
{
switchIndex = i + randomint( n - i );
temp = permutation[ switchIndex ];
permutation[ SwitchIndex ] = permutation[ i ];
permutation[ i ] = temp;
}
}
return permutation;
}
callOptionalBehaviorCallback( callback, arg, arg2, arg3 )
{
if ( !isdefined( callback ) )
return false;
//prof_begin( "callOptionalBehaviorCallback" );
self thread endIdleAtFrameEnd();
starttime = gettime();
val = undefined;
if ( isdefined( arg3 ) )
val = [[ callback ]]( arg, arg2, arg3 );
else if ( isdefined( arg2 ) )
val = [[ callback ]]( arg, arg2 );
else if ( isdefined( arg ) )
val = [[ callback ]]( arg );
else
val = [[ callback ]]();
/#
// if this assert fails, a behaviorCallback callback didn't return true or false.
assert( isdefined( val ) && ( val == true || val == false ) );
// behaviorCallbacks must return true if and only if they let time pass.
// (it is also important that they only let time pass if they did what they were supposed to do,
// but that's not so easy to enforce.)
if ( val )
assert( gettime() != starttime );
else
assert( gettime() == starttime );
#/
if ( !val )
self notify( "dont_end_idle" );
//prof_end( "callOptionalBehaviorCallback" );
return val;
}
watchSuppression()
{
self endon( "killanimscript" );
// self.lastSuppressionTime is the last time a bullet whizzed by.
// self.suppressionStart is the last time we were thinking it was safe when a bullet whizzed by.
self.lastSuppressionTime = gettime() - 100000;
self.suppressionStart = self.lastSuppressionTime;
while ( 1 )
{
self waittill( "suppression" );
time = gettime();
if ( self.lastSuppressionTime < time - 700 )
self.suppressionStart = time;
self.lastSuppressionTime = time;
}
}
coverReload( behaviorCallbacks, threshold )
{
if ( self.bulletsInClip > weaponClipSize( self.weapon ) * threshold )
return false;
self.isreloading = true;
result = callOptionalBehaviorCallback( behaviorCallbacks.reload );
self.isreloading = false;
return result;
}
// initialGoal can be either "normal", "suppress", or "ambush".
leaveCoverAndShoot( behaviorCallbacks, initialGoal )
{
self thread animscripts\shoot_behavior::decideWhatAndHowToShoot( initialGoal );
if ( !self.fixedNode && !self.doingAmbush )
self thread breakOutOfShootingIfWantToMoveUp();
val = callOptionalBehaviorCallback( behaviorCallbacks.leaveCoverAndShoot );
self notify( "stop_deciding_how_to_shoot" );
return val;
}
lookForEnemy( behaviorCallbacks )
{
if ( self.a.atConcealmentNode && self canSeeEnemy() )
return false;
if ( self.a.lastEncounterTime + 6000 > gettime() )
{
return lookfast( behaviorCallbacks );
}
else
{
// look slow if possible
result = callOptionalBehaviorCallback( behaviorCallbacks.look, 2 + randomfloat( 2 ) );
if ( result )
return true;
return callOptionalBehaviorCallback( behaviorCallbacks.fastlook );
}
}
lookfast( behaviorCallbacks )
{
// look fast if possible
result = callOptionalBehaviorCallback( behaviorCallbacks.fastlook );
if ( result )
return true;
return callOptionalBehaviorCallback( behaviorCallbacks.look, 0 );
}
idle( behaviorCallbacks, howLong )
{
self.flinching = false;
if ( isdefined( behaviorCallbacks.flinch ) )
{
// flinch if we just started getting shot at very recently
if ( !self.a.idlingAtCover && gettime() - self.suppressionStart < 600 )
{
if ( [[ behaviorCallbacks.flinch ]]() )
return true;
}
else
{
// if bullets aren't already whizzing by, idle for now but flinch if we get incoming fire
self thread flinchWhenSuppressed( behaviorCallbacks );
}
}
if ( !self.a.idlingAtCover )
{
assert( isdefined( behaviorCallbacks.idle ) );// idle must be available!
self thread idleThread( behaviorCallbacks.idle );// this thread doesn't stop until "end_idle", which must be notified before we start anything else! use endIdleAtFrameEnd() to do this.
self.a.idlingAtCover = true;
}
if ( isdefined( howLong ) )
self idleWait( howLong );
else
self idleWaitABit();
if ( self.flinching )
self waittill( "flinch_done" );
self notify( "stop_waiting_to_flinch" );
}
idleWait( howLong )
{
self endon( "end_idle" );
wait howLong;
}
idleWaitAbit()
{
self endon( "end_idle" );
wait 0.3 + randomfloat( 0.1 );
self waittill( "do_slow_things" );
}
idleThread( idlecallback )
{
self endon( "killanimscript" );
self [[ idlecallback ]]();
}
flinchWhenSuppressed( behaviorCallbacks )
{
self endon( "killanimscript" );
self endon( "stop_waiting_to_flinch" );
lastSuppressionTime = self.lastSuppressionTime;
while ( 1 )
{
self waittill( "suppression" );
time = gettime();
if ( lastSuppressionTime < time - 2000 )
break;
lastSuppressionTime = time;
}
self.flinching = true;
self thread endIdleAtFrameEnd();
assert( isdefined( behaviorCallbacks.flinch ) );
val = [[ behaviorCallbacks.flinch ]]();
if ( !val )
self notify( "dont_end_idle" );
self.flinching = false;
self notify( "flinch_done" );
}
endIdleAtFrameEnd()
{
self endon( "killanimscript" );
self endon( "dont_end_idle" );
waittillframeend;
if ( !isdefined( self ) )
return;
self notify( "end_idle" );
self.a.idlingAtCover = false;
}
tryThrowingGrenade( behaviorCallbacks, throwAt )
{
assert( isdefined( throwAt ) );
// don't throw backwards
forward = anglesToForward( self.angles );
dir = vectorNormalize( throwAt.origin - self.origin );
if ( vectorDot( forward, dir ) < 0 )
return false;
if ( self.doingAmbush && !recentlySawEnemy() )
return false;
if ( self isPartiallySuppressedWrapper() )
{
return callOptionalBehaviorCallback( behaviorCallbacks.grenadehidden, throwAt );
}
else
{
return callOptionalBehaviorCallback( behaviorCallbacks.grenade, throwAt );
}
}
blindfire( behaviorCallbacks )
{
if ( !canBlindFire() )
return false;
return callOptionalBehaviorCallback( behaviorCallbacks.blindfire );
}
// Need this?
breakOutOfShootingIfWantToMoveUp()
{
self endon( "killanimscript" );
self endon( "stop_deciding_how_to_shoot" );
while ( 1 )
{
if ( self.fixedNode || self.doingAmbush )
return;
wait 0.5 + randomfloat( 0.75 );
if ( !isdefined( self.enemy ) )
continue;
if ( enemyIsHiding() )
{
if ( advanceOnHidingEnemy() )
return;
}
if ( !self recentlySawEnemy() && !self canSuppressEnemy() )
{
if ( gettime() > self.a.getBoredOfThisNodeTime )
{
if ( cantFindAnythingToDo() )
return;
}
}
}
}
enemyIsHiding()
{
// if this function is called, we already know that our enemy is not visible from exposed.
// check to see if they're doing anything hiding-like.
if ( !isdefined( self.enemy ) )
return false;
if ( self.enemy isFlashed() )
return true;
if ( isplayer( self.enemy ) )
{
if ( isdefined( self.enemy.health ) && self.enemy.health < self.enemy.maxhealth )
return true;
}
else
{
if ( isAI( self.enemy ) && self.enemy isSuppressedWrapper() )
return true;
}
if ( isdefined( self.enemy.isreloading ) && self.enemy.isreloading )
return true;
return false;
}
resetRespondToDeathTime()
{
self.a.respondToDeathTime = 0;
}
resetLookForBetterCoverTime()
{
currentTime = gettime();
// treat group of shuffle nodes as one node, don't increase getBoredOfThisNodeTime by too much
if ( isdefined( self.didShuffleMove ) && currentTime > self.a.getBoredOfThisNodeTime )
{
self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 );
}
else if ( isdefined( self.enemy ) )
{
dist = distance2D( self.origin, self.enemy.origin );
if ( dist < self.engageMinDist )
self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 10000 );
else if ( dist > self.engageMaxDist && dist < self.goalradius )
self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 2000, 5000 );
else
self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 10000, 15000 );
}
else
{
self.a.getBoredOfThisNodeTime = currentTime + randomintrange( 5000, 15000 );
}
}
resetSeekOutEnemyTime()
{
// we'll be willing to actually run right up to our enemy in order to find them if we haven't seen them by this time.
// however, we'll try to find better cover before seeking them out
if ( isdefined( self.aggressiveMode ) )
self.seekOutEnemyTime = gettime() + randomintrange( 500, 1000 );
else
self.seekOutEnemyTime = gettime() + randomintrange( 3000, 5000 );
}
// these next functions are "look for better cover" functions.
// they don't always need to cause the actor to leave the node immediately,
// but if they keep being called over and over they need to become more and more likely to do so,
// as this indicates that new cover is strongly needed.
cantFindAnythingToDo()
{
return advanceOnHidingEnemy();
}
advanceOnHidingEnemy()
{
if ( self.fixedNode || self.doingAmbush )
return false;
if ( isdefined( self.aggressiveMode ) && gettime() >= self.seekOutEnemyTime )
{
return tryRunningToEnemy( false );
}
foundBetterCover = false;
if ( !isdefined( self.enemy ) || !self.enemy isFlashed() )
foundBetterCover = lookForBetterCover();
if ( !foundBetterCover && isdefined( self.enemy ) && !self canSeeEnemyFromExposed() )
{
if ( gettime() >= self.seekOutEnemyTime )
{
return tryRunningToEnemy( false );
}
}
// maybe at this point we could look for someone who's suppressing our enemy,
// and if someone is, we can say "cover me!" and have them say "i got you covered" or something.
return foundBetterCover;
}
tryToGetOutOfDangerousSituation( behaviorCallbacks )
{
if ( isdefined( behaviorCallbacks.moveToNearByCover ) )
{
if ( [[ behaviorCallbacks.moveToNearByCover ]]() )
return true;
}
return lookForBetterCover();
}
// TEMP move these into animsets
set_standing_turns()
{
self.a.array[ "turn_left_45" ] = %exposed_tracking_turn45L;
self.a.array[ "turn_left_90" ] = %exposed_tracking_turn90L;
self.a.array[ "turn_left_135" ] = %exposed_tracking_turn135L;
self.a.array[ "turn_left_180" ] = %exposed_tracking_turn180L;
self.a.array[ "turn_right_45" ] = %exposed_tracking_turn45R;
self.a.array[ "turn_right_90" ] = %exposed_tracking_turn90R;
self.a.array[ "turn_right_135" ] = %exposed_tracking_turn135R;
self.a.array[ "turn_right_180" ] = %exposed_tracking_turn180R;
}
set_crouching_turns()
{
self.a.array[ "turn_left_45" ] = %exposed_crouch_turn_90_left;
self.a.array[ "turn_left_90" ] = %exposed_crouch_turn_90_left;
self.a.array[ "turn_left_135" ] = %exposed_crouch_turn_180_left;
self.a.array[ "turn_left_180" ] = %exposed_crouch_turn_180_left;
self.a.array[ "turn_right_45" ] = %exposed_crouch_turn_90_right;
self.a.array[ "turn_right_90" ] = %exposed_crouch_turn_90_right;
self.a.array[ "turn_right_135" ] = %exposed_crouch_turn_180_right;
self.a.array[ "turn_right_180" ] = %exposed_crouch_turn_180_right;
}
turnToMatchNodeDirection( nodeAngleOffset )
{
if ( isdefined( self.node ) )
{
node = self.node;
absRelYaw = abs( AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) ) );
if ( self.a.pose == "stand" && node getHighestNodeStance() != "stand" )
{
if ( absRelYaw > 45 && absRelYaw < 90 )
self orientmode( "face angle", self.angles[1] );
else
self orientmode( "face current" );
rate = 1.5;
noteTime = getNotetrackTimes( %exposed_stand_2_crouch, "anim_pose = \"crouch\"" )[0];
noteTime = min( 1, noteTime * 1.1 );
time = noteTime * getAnimLength( %exposed_stand_2_crouch ) / rate;
self setflaggedanimknoballrestart( "crouchanim", %exposed_stand_2_crouch, %body, 1, .2, rate );
self animscripts\shared::DoNoteTracksForTime( time, "crouchanim" );
self clearanim( %body, 0.2 );
}
self orientmode( "face angle", self.angles[1] );
relYaw = AngleClamp180( self.angles[1] - ( node.angles[1] + nodeAngleOffset ) );
if ( abs( relYaw ) > 45 )
{
if ( self.a.pose == "stand" )
set_standing_turns();
else
set_crouching_turns();
self.turnThreshold = 45;
self.turnToMatchNode = true;
animscripts\combat::TurnToFaceRelativeYaw( relYaw );
self.turnToMatchNode = undefined;
}
}
}
moveToNearbyCover()
{
if ( !isdefined( self.enemy ) )
return false;
if ( isdefined( self.didShuffleMove ) )
{
self.didShuffleMove = undefined;
return false;
}
if ( !isdefined( self.node ) )
return false;
if ( randomint( 3 ) == 0 )
return false;
if ( self.fixedNode || self.doingAmbush || self.keepClaimedNode || self.keepClaimedNodeIfValid )
return false;
if ( distanceSquared( self.origin, self.node.origin ) > 16 * 16 )
return false;
node = self findshufflecovernode();
if ( isdefined( node ) && ( node != self.node ) && self useCoverNode( node ) )
{
self.shuffleMove = true;
self.shuffleNode = node;
self.didShuffleMove = true;
// give code a chance use new cover node
wait 0.5;
return true;
}
return false;
}