IW4-Dump-Files/animscripts/cover_arrival.gsc

1608 lines
43 KiB
Plaintext

#include animscripts\SetPoseMovement;
#include animscripts\combat_utility;
#include animscripts\utility;
#include animscripts\animset;
#include common_scripts\utility;
#include maps\_utility;
#using_animtree( "generic_human" );
// constants for exposed approaches
maxSpeed = 250;// units / sec
allowedError = 8;
main()
{
self endon( "killanimscript" );
self endon( "abort_approach" );
approachnumber = self.approachNumber;
assert( isdefined( self.approachtype ) );
arrivalAnim = anim.coverTrans[ self.approachtype ][ approachnumber ];
assert( isdefined( arrivalAnim ) );
if ( !isdefined( self.heat ) )
self thread abortApproachIfThreatened();
self clearanim( %body, 0.2 );
self setFlaggedAnimRestart( "coverArrival", arrivalAnim, 1, 0.2, self.moveTransitionRate );
self animscripts\shared::DoNoteTracks( "coverArrival", ::handleStartAim );
newstance = anim.arrivalEndStance[ self.approachType ];
assertex( isdefined( newstance ), "bad node approach type: " + self.approachtype );
if ( isdefined( newstance ) )
self.a.pose = newstance;
self.a.movement = "stop";
self.a.arrivalType = self.approachType;
// we rely on cover to start doing something else with animations very soon.
// in the meantime, we don't want any of our parent nodes lying around with positive weights.
self clearanim( %root, .3 );
self.lastApproachAbortTime = undefined;
}
handleStartAim( note )
{
if ( note == "start_aim" )
{
if ( self.a.pose == "stand" )
{
self set_animarray_standing();
}
else if ( self.a.pose == "crouch" )
{
self set_animarray_crouching();
}
else
{
assertMsg( "Unsupported self.a.pose: " + self.a.pose );
}
self animscripts\combat::set_aim_and_turn_limits();
self.previousPitchDelta = 0.0;
setupAim( 0 );
self thread animscripts\shared::trackShootEntOrPos();
}
}
isThreatenedByEnemy()
{
if ( !isdefined( self.node ) )
return false;
if ( isdefined( self.enemy ) && self seeRecently( self.enemy, 1.5 ) && distanceSquared( self.origin, self.enemy.origin ) < 250000 )
return !( self isCoverValidAgainstEnemy() );
return false;
}
abortApproachIfThreatened()
{
self endon( "killanimscript" );
while ( 1 )
{
if ( !isdefined( self.node ) )
return;
if ( isThreatenedByEnemy() )
{
self clearanim( %root, .3 );
self notify( "abort_approach" );
self.lastApproachAbortTime = getTime();
return;
}
wait 0.1;
}
}
getNodeStanceYawOffset( approachtype )
{
// returns the base stance's yaw offset when hiding at a node, based off the approach type
if ( isdefined( self.heat ) )
return 0;
if ( approachtype == "left" || approachtype == "left_crouch" )
return 90.0;
else if ( approachtype == "right" || approachtype == "right_crouch" )
return - 90.0;
return 0;
}
canUseSawApproach( node )
{
if ( !usingMG() )
return false;
if ( !isDefined( node.turretInfo ) )
return false;
if ( node.type != "Cover Stand" && node.type != "Cover Prone" && node.type!= "Cover Crouch" )
return false;
if ( isDefined( self.enemy ) && distanceSquared( self.enemy.origin, node.origin ) < 256 * 256 )
return false;
if ( GetNodeYawToEnemy() > 40 || GetNodeYawToEnemy() < - 40 )
return false;
return true;
}
determineNodeApproachType( node )
{
if ( canUseSawApproach( node ) )
{
if ( node.type == "Cover Stand" )
return "stand_saw";
if ( node.type == "Cover Crouch" )
return "crouch_saw";
else if ( node.type == "Cover Prone" )
return "prone_saw";
}
if ( !isdefined( anim.approach_types[ node.type ] ) )
return;
if ( isdefined( node.arrivalStance ) )
stance = node.arrivalStance;
else
stance = node getHighestNodeStance();
// no approach to prone
if ( stance == "prone" )
stance = "crouch";
type = anim.approach_types[ node.type ][ stance ];
if ( self shouldCQB() )
{
cqbType = type + "_cqb";
if ( isdefined( anim.coverTrans[ cqbType ] ) )
type = cqbType;
}
return type;
}
determineNodeExitType( node )
{
if ( canUseSawApproach( node ) )
{
if ( node.type == "Cover Stand" )
return "stand_saw";
if ( node.type == "Cover Crouch" )
return "crouch_saw";
else if ( node.type == "Cover Prone" )
return "prone_saw";
}
if ( !isdefined( anim.approach_types[ node.type ] ) )
return;
if ( isdefined( anim.requiredExitStance[ node.type ] ) && anim.requiredExitStance[ node.type ] != self.a.pose )
return;
stance = self.a.pose;
// no exit from prone
if ( stance == "prone" )
stance = "crouch";
type = anim.approach_types[ node.type ][ stance ];
if ( self shouldCQB() )
{
cqbType = type + "_cqb";
if ( isdefined( anim.coverExit[ cqbType ] ) )
type = cqbType;
}
return type;
}
determineExposedApproachType( node )
{
if ( isdefined( self.heat ) )
{
return "heat";
}
if ( isdefined( node.arrivalStance ) )
stance = node.arrivalStance;
else
stance = node getHighestNodeStance();
// no approach to prone
if ( stance == "prone" )
stance = "crouch";
if ( stance == "crouch" )
type = "exposed_crouch";
else
type = "exposed";
if ( shouldCQB() )
return type + "_cqb";
return type;
}
getMaxDirectionsAndExcludeDirFromApproachType( node )
{
returnobj = spawnstruct();
if ( isdefined( node ) && isdefined( anim.maxDirections[ node.type ] ) )
{
returnobj.maxDirections = anim.maxDirections[ node.type ];
returnobj.excludeDir = anim.excludeDir[ node.type ];
}
else
{
returnobj.maxDirections = 9;
returnobj.excludeDir = -1;
}
return returnobj;
}
shouldApproachToExposed( approachType )
{
// decide whether it's a good idea to go directly into the exposed position as we approach this node.
if ( !isdefined( self.enemy ) )
return false;// nothing to shoot!
if ( self NeedToReload( 0.5 ) )
return false;
if ( self isSuppressedWrapper() )
return false;// too dangerous, we need cover
// path nodes have no special "exposed" position
if ( isdefined( anim.exposedTransition[ approachtype ] ) )
return false;
// no arrival animations into exposed for left/right crouch
if ( approachtype == "left_crouch" || approachtype == "right_crouch" )
return false;
return canSeePointFromExposedAtNode( self.enemy getShootAtPos(), self.node );
}
calculateNodeOffsetFromAnimationDelta( nodeAngles, delta )
{
// in the animation, forward = +x and right = -y
right = anglestoright( nodeAngles );
forward = anglestoforward( nodeAngles );
return vector_multiply( forward, delta[ 0 ] ) + vector_multiply( right, 0 - delta[ 1 ] );
}
getApproachEnt()
{
if ( isdefined( self.scriptedArrivalEnt ) )
return self.scriptedArrivalEnt;
if ( isdefined( self.node ) )
return self.node;
return undefined;
}
getApproachPoint( node, approachtype )
{
if ( approachType == "stand_saw" )
{
approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] );
forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
approachPoint = approachPoint + vector_multiply( forward, -32.545 ) - vector_multiply( right, 6.899 ); // -41.343 would work better for the first number if that weren't too far from the node =(
}
else if ( approachType == "crouch_saw" )
{
approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] );
forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
approachPoint = approachPoint + vector_multiply( forward, -32.545 ) - vector_multiply( right, 6.899 );
}
else if ( approachType == "prone_saw" )
{
approachPoint = ( node.turretInfo.origin[ 0 ], node.turretInfo.origin[ 1 ], node.origin[ 2 ] );
forward = anglesToForward( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
right = anglesToRight( ( 0, node.turretInfo.angles[ 1 ], 0 ) );
approachPoint = approachPoint + vector_multiply( forward, -37.36 ) - vector_multiply( right, 13.279 );
}
else if ( isdefined( self.scriptedArrivalEnt ) )
{
approachPoint = self.goalpos;
}
else
{
approachPoint = node.origin;
}
return approachPoint;
}
checkApproachPreConditions()
{
// if we're going to do a negotiation, we want to wait until it's over and move.gsc is called again
if ( isdefined( self getnegotiationstartnode() ) )
{
/# debug_arrival( "Not doing approach: path has negotiation start node" ); #/
return false;
}
if ( isdefined( self.disableArrivals ) && self.disableArrivals )
{
/# debug_arrival( "Not doing approach: self.disableArrivals is true" ); #/
return false;
}
/#
if ( isdefined( self.disableCoverArrivalsOnly ) )
{
debug_arrival( "Not doing approach: self.disableCoverArrivalsOnly is true" );
return false;
}
#/
/*if ( self shouldCQB() )
{
/# debug_arrival("Not doing approach: self.cqbwalking is true"); #/
return false;
}*/
return true;
}
checkApproachConditions( approachType, approach_dir, node )
{
// we're doing default exposed approaches in doLastMinuteExposedApproach now
if ( isdefined( anim.exposedTransition[ approachtype ] ) )
return false;
if ( approachType == "stand" || approachType == "crouch" )
{
assert( isdefined( node ) );
if ( AbsAngleClamp180( vectorToYaw( approach_dir ) - node.angles[ 1 ] + 180 ) < 60 )
{
/# debug_arrival( "approach aborted: approach_dir is too far forward for node type " + node.type ); #/
return false;
}
}
if ( self isThreatenedByEnemy() || ( isdefined( self.lastApproachAbortTime ) && self.lastApproachAbortTime + 500 > getTime() ) )
{
/# debug_arrival( "approach aborted: nearby enemy threat" ); #/
return false;
}
return true;
}
/#
setupApproachNode_debugInfo( actor, approachType, approach_dir, approachNodeYaw, node )
{
if ( debug_arrivals_on_actor() )
{
println( "^5approaching cover (ent " + actor getentnum() + ", type \"" + approachType + "\"):" );
println( " approach_dir = (" + approach_dir[ 0 ] + ", " + approach_dir[ 1 ] + ", " + approach_dir[ 2 ] + ")" );
angle = AngleClamp180( vectortoyaw( approach_dir ) - approachNodeYaw + 180 );
if ( angle < 0 )
println( " (Angle of " + ( 0 - angle ) + " right from node forward.)" );
else
println( " (Angle of " + angle + " left from node forward.)" );
if ( approachType == "exposed" )
{
if ( isdefined( node ) )
{
if ( isdefined( approachtype ) )
debug_arrival( "Aborting cover approach: node approach type was " + approachtype );
else
debug_arrival( "Aborting cover approach: node approach type was undefined" );
}
else
{
debug_arrival( "Aborting cover approach: node is undefined" );
}
}
else
{
thread drawApproachVec( approach_dir );
}
}
}
#/
setupApproachNode( firstTime )
{
self endon( "killanimscript" );
//self endon("path_changed");
if ( isdefined( self.heat ) )
{
self thread doLastMinuteExposedApproachWrapper();
return;
}
// this lets code know that script is expecting the "cover_approach" notify
if ( firstTime )
self.requestArrivalNotify = true;
self.a.arrivalType = undefined;
self thread doLastMinuteExposedApproachWrapper();
self waittill( "cover_approach", approach_dir );
if ( !self checkApproachPreConditions() )
return;
self thread setupApproachNode( false ); // wait again incase path goal changes
approachType = "exposed";
approachPoint = self.pathGoalPos;
approachNodeYaw = vectorToYaw( approach_dir );
approachFinalYaw = approachNodeYaw;
node = getApproachEnt();
if ( isdefined( node ) )
{
approachType = determineNodeApproachType( node );
if ( isdefined( approachtype ) && approachtype != "exposed" )
{
approachPoint = getApproachPoint( node, approachtype );
approachNodeYaw = node.angles[ 1 ];
approachFinalYaw = getNodeForwardYaw( node );
}
}
/# setupApproachNode_debugInfo( self, approachType, approach_dir, approachNodeYaw, node ); #/
if ( !checkApproachConditions( approachType, approach_dir, node ) )
return;
startCoverApproach( approachType, approachPoint, approachNodeYaw, approachFinalYaw, approach_dir );
}
coverApproachLastMinuteCheck( approachPoint, approachFinalYaw, approachType, approachNumber, requiredYaw )
{
if ( isdefined( self.disableArrivals ) && self.disableArrivals )
{
/# debug_arrival( "approach aborted at last minute: self.disableArrivals is true" ); #/
return false;
}
// so we don't make guys turn around when they're (smartly) facing their enemy as they walk away
if ( abs( self getMotionAngle() ) > 45 && isdefined( self.enemy ) && vectorDot( anglesToForward( self.angles ), vectorNormalize( self.enemy.origin - self.origin ) ) > .8 )
{
/# debug_arrival( "approach aborted at last minute: facing enemy instead of current motion angle" ); #/
return false;
}
if ( self.a.pose != "stand" || ( self.a.movement != "run" && !( self isCQBWalkingOrFacingEnemy() ) ) )
{
/# debug_arrival( "approach aborted at last minute: not standing and running" ); #/
return false;
}
if ( AbsAngleClamp180( requiredYaw - self.angles[ 1 ] ) > 30 )
{
// don't do an approach away from an enemy that we would otherwise face as we moved away from them
if ( isdefined( self.enemy ) && self canSee( self.enemy ) && distanceSquared( self.origin, self.enemy.origin ) < 256 * 256 )
{
// check if enemy is in frontish of us
if ( vectorDot( anglesToForward( self.angles ), self.enemy.origin - self.origin ) > 0 )
{
/# debug_arrival( "aborting approach at last minute: don't want to turn back to nearby enemy" ); #/
return false;
}
}
}
// make sure the path is still clear
if ( !checkCoverEnterPos( approachPoint, approachFinalYaw, approachType, approachNumber, false ) )
{
/# debug_arrival( "approach blocked at last minute" ); #/
return false;
}
return true;
}
approachWaitTillClose( node, checkDist )
{
if ( !isdefined( node ) )
return;
// wait until we get to the point where we have to decide what approach animation to play
while ( 1 )
{
if ( !isdefined( self.pathGoalPos ) )
self waitForPathGoalPos();
dist = distance( self.origin, self.pathGoalPos );
if ( dist <= checkDist + allowedError )
break;
// underestimate how long to wait so we don't miss the crucial point
waittime = ( dist - checkDist ) / maxSpeed - .1;
if ( waittime < .05 )
waittime = .05;
wait waittime;
}
}
startCoverApproach( approachType, approachPoint, approachNodeYaw, approachFinalYaw, approach_dir )
{
self endon( "killanimscript" );
self endon( "cover_approach" );
assert( isdefined( approachType ) );
assert( approachType != "exposed" );
node = getApproachEnt();
result = getMaxDirectionsAndExcludeDirFromApproachType( node );
maxDirections = result.maxDirections;
excludeDir = result.excludeDir;
arrivalFromFront = vectorDot( approach_dir, anglestoforward( node.angles ) ) >= 0;
// find best possible position to start arrival animation
result = self CheckArrivalEnterPositions( approachPoint, approachFinalYaw, approachType, approach_dir, maxDirections, excludeDir, arrivalFromFront );
if ( result.approachNumber < 0 )
{
/# debug_arrival( "approach aborted: " + result.failure ); #/
return;
}
approachNumber = result.approachNumber;
/# debug_arrival( "approach success: dir " + approachNumber ); #/
if ( level.newArrivals && approachNumber <= 6 && arrivalFromFront )
{
self endon( "goal_changed" );
self.arrivalStartDist = anim.coverTransLongestDist[ approachtype ];
approachWaitTillClose( node, self.arrivalStartDist );
// get the best approach direction from current position
dirToNode = vectorNormalize( approachPoint - self.origin );
result = self CheckArrivalEnterPositions( approachPoint, approachFinalYaw, approachType, dirToNode, maxDirections, excludeDir, arrivalFromFront );
self.arrivalStartDist = length( anim.coverTransDist[ approachtype ][ approachNumber ] );
approachWaitTillClose( node, self.arrivalStartDist );
if ( !( self maymovetopoint( approachPoint ) ) )
{
/# debug_arrival( "approach blocked at last minute" ); #/
self.arrivalStartDist = undefined;
return;
}
if ( result.approachNumber < 0 )
{
/# debug_arrival( "final approach aborted: " + result.failure ); #/
self.arrivalStartDist = undefined;
return;
}
approachNumber = result.approachNumber;
/# debug_arrival( "final approach success: dir " + approachNumber ); #/
requiredYaw = approachFinalYaw - anim.coverTransAngles[ approachType ][ approachNumber ];
}
else
{
// set arrival position and wait
self setRunToPos( self.coverEnterPos );
self waittill( "runto_arrived" );
requiredYaw = approachFinalYaw - anim.coverTransAngles[ approachType ][ approachNumber ];
if ( !self coverApproachLastMinuteCheck( approachPoint, approachFinalYaw, approachType, approachNumber, requiredYaw ) )
return;
}
self.approachNumber = approachNumber; // used in cover_arrival::main()
self.approachType = approachType;
self.arrivalStartDist = undefined;
self startcoverarrival( self.coverEnterPos, requiredYaw );
}
CheckArrivalEnterPositions( approachpoint, approachYaw, approachtype, approach_dir, maxDirections, excludeDir, arrivalFromFront )
{
assert( approachtype != "exposed" );
angleDataObj = spawnstruct();
calculateNodeTransitionAngles( angleDataObj, approachtype, true, approachYaw, approach_dir, maxDirections, excludeDir );
sortNodeTransitionAngles( angleDataObj, maxDirections );
resultobj = spawnstruct();
/#resultobj.data = [];#/
arrivalPos = ( 0, 0, 0 );
resultobj.approachNumber = -1;
numAttempts = 2;
for ( i = 1; i <= numAttempts; i++ )
{
assert( angleDataObj.transIndex[ i ] != excludeDir );// shouldn't hit excludeDir unless numAttempts is too big
resultobj.approachNumber = angleDataObj.transIndex[ i ];
if ( !self checkCoverEnterPos( approachpoint, approachYaw, approachtype, resultobj.approachNumber, arrivalFromFront ) )
{
/#resultobj.data[ resultobj.data.size ] = "approach blocked: dir " + resultobj.approachNumber;#/
continue;
}
break;
}
if ( i > numAttempts )
{
/#resultobj.failure = numAttempts + " direction attempts failed";#/
resultobj.approachNumber = -1;
return resultobj;
}
// if AI is closer to node than coverEnterPos is, don't do arrival
distToApproachPoint = distanceSquared( approachpoint, self.origin );
distToAnimStart = distanceSquared( approachpoint, self.coverEnterPos );
if ( distToApproachPoint < distToAnimStart * 2 * 2 )
{
if ( distToApproachPoint < distToAnimStart )
{
/#resultobj.failure = "too close to destination";#/
resultobj.approachNumber = -1;
return resultobj;
}
if ( !level.newArrivals || !arrivalFromFront )
{
// if AI is less than twice the distance from the node than the beginning of the approach animation,
// make sure the angle we'll turn when we start the animation is small.
selfToAnimStart = vectorNormalize( self.coverEnterPos - self.origin );
requiredYaw = approachYaw - anim.coverTransAngles[ approachType ][ resultobj.approachNumber ];
AnimStartToNode = anglesToForward( ( 0, requiredYaw, 0 ) );
cosAngle = vectorDot( selfToAnimStart, AnimStartToNode );
if ( cosAngle < 0.707 )// 0.707 == cos( 45 )
{
/#resultobj.failure = "angle to start of animation is too great (angle of " + acos( cosAngle ) + " > 45)";#/
resultobj.approachNumber = -1;
return resultobj;
}
}
}
/#
for ( i = 0; i < resultobj.data.size; i++ )
debug_arrival( resultobj.data[ i ] );
#/
return resultobj;
}
doLastMinuteExposedApproachWrapper()
{
self endon( "killanimscript" );
self endon( "move_interrupt" );
self notify( "doing_last_minute_exposed_approach" );
self endon( "doing_last_minute_exposed_approach" );
self thread watchGoalChanged();
while ( 1 )
{
doLastMinuteExposedApproach();
// try again when our goal pos changes
while ( 1 )
{
self waittill_any( "goal_changed", "goal_changed_previous_frame" );
// our goal didn't *really* change if it only changed because we called setRunToPos
if ( isdefined( self.coverEnterPos ) && isdefined( self.pathGoalPos ) && distance2D( self.coverEnterPos, self.pathGoalPos ) < 1 )
continue;
break;
}
}
}
watchGoalChanged()
{
self endon( "killanimscript" );
self endon( "doing_last_minute_exposed_approach" );
while ( 1 )
{
self waittill( "goal_changed" );
wait .05;
self notify( "goal_changed_previous_frame" );
}
}
exposedApproachConditionCheck( node, goalMatchesNode )
{
if ( !isdefined( self.pathGoalPos ) )
{
/# debug_arrival( "Aborting exposed approach because I have no path" ); #/
return false;
}
if ( isdefined( self.disableArrivals ) && self.disableArrivals )
{
/# debug_arrival( "Aborting exposed approach because self.disableArrivals is true" ); #/
return false;
}
if ( isdefined( self.approachConditionCheckFunc ) )
{
if ( !self [[self.approachConditionCheckFunc]]( node ) )
return false;
}
else
{
if ( !self.faceMotion && ( !isdefined( node ) || node.type == "Path" ) )
{
/# debug_arrival( "Aborting exposed approach because not facing motion and not going to a node" ); #/
return false;
}
if ( self.a.pose != "stand" )
{
/# debug_arrival( "approach aborted at last minute: not standing" ); #/
return false;
}
}
if ( self isThreatenedByEnemy() || ( isdefined( self.lastApproachAbortTime ) && self.lastApproachAbortTime + 500 > getTime() ) )
{
/# debug_arrival( "approach aborted: nearby enemy threat" ); #/
return false;
}
// only do an arrival if we have a clear path
if ( !self maymovetopoint( self.pathGoalPos ) )
{
/#debug_arrival( "Aborting exposed approach: maymove check failed" );#/
return false;
}
return true;
}
exposedApproachWaitTillClose()
{
// wait until we get to the point where we have to decide what approach animation to play
while ( 1 )
{
if ( !isdefined( self.pathGoalPos ) )
self waitForPathGoalPos();
node = getApproachEnt();
if ( isdefined( node ) && !isdefined( self.heat ) )
arrivalPos = node.origin;
else
arrivalPos = self.pathGoalPos;
dist = distance( self.origin, arrivalPos );
checkDist = anim.longestExposedApproachDist;
if ( dist <= checkDist + allowedError )
break;
// underestimate how long to wait so we don't miss the crucial point
waittime = ( dist - anim.longestExposedApproachDist ) / maxSpeed - .1;
if ( waittime < 0 )
break;
if ( waittime < .05 )
waittime = .05;
// /#self thread animscripts\shared::showNoteTrack("wait " + waittime);#/
wait waittime;
}
}
faceEnemyAtEndOfApproach( node )
{
if ( !isdefined( self.enemy ) )
return false;
if ( isdefined( self.heat ) && isdefined( node ) )
return false;
if ( self.combatmode == "cover" && isSentient( self.enemy ) && gettime() - self lastKnownTime( self.enemy ) > 15000 )
return false;
return sightTracePassed( self.enemy getShootAtPos(), self.pathGoalPos + ( 0, 0, 60 ), false, undefined );
}
doLastMinuteExposedApproach()
{
self endon( "goal_changed" );
self endon( "move_interrupt" );
if ( isdefined( self getnegotiationstartnode() ) )
return;
self exposedApproachWaitTillClose();
if ( isdefined( self.grenade ) && isdefined( self.grenade.activator ) && self.grenade.activator == self )
return;
approachType = "exposed";
maxDistToNodeSq = 1;
if ( isdefined( self.approachTypeFunc ) )
{
approachtype = self [[ self.approachTypeFunc ]]();
}
else if ( self shouldCQB() )
{
approachtype = "exposed_cqb";
}
else if ( isdefined( self.heat ) )
{
approachtype = "heat";
maxDistToNodeSq = 64 * 64;
}
node = getApproachEnt();
if ( isdefined( node ) && isdefined( self.pathGoalPos ) && !isdefined( self.disableCoverArrivalsOnly ) )
goalMatchesNode = distanceSquared( self.pathGoalPos, node.origin ) < maxDistToNodeSq;
else
goalMatchesNode = false;
if ( goalMatchesNode )
approachtype = determineExposedApproachType( node );
approachDir = VectorNormalize( self.pathGoalPos - self.origin );
// by default, want to face forward
desiredFacingYaw = vectorToYaw( approachDir );
if ( isdefined( self.faceEnemyArrival ) )
{
desiredFacingYaw = self.angles[1];
}
else if ( faceEnemyAtEndOfApproach( node ) )
{
desiredFacingYaw = vectorToYaw( self.enemy.origin - self.pathGoalPos );
}
else
{
faceNodeAngle = isdefined( node ) && goalMatchesNode;
faceNodeAngle = faceNodeAngle && ( node.type != "Path" ) && ( node.type != "Ambush" || !recentlySawEnemy() );
if ( faceNodeAngle )
{
desiredFacingYaw = getNodeForwardYaw( node );
}
else
{
likelyEnemyDir = self getAnglesToLikelyEnemyPath();
if ( isdefined( likelyEnemyDir ) )
desiredFacingYaw = likelyEnemyDir[ 1 ];
}
}
angleDataObj = spawnstruct();
calculateNodeTransitionAngles( angleDataObj, approachType, true, desiredFacingYaw, approachDir, 9, -1 );
// take best animation
best = 1;
for ( i = 2; i <= 9; i++ )
{
if ( angleDataObj.transitions[ i ] > angleDataObj.transitions[ best ] )
best = i;
}
self.approachNumber = angleDataObj.transIndex[ best ];
self.approachType = approachType;
/# debug_arrival( "Doing exposed approach in direction " + self.approachNumber ); #/
approachAnim = anim.coverTrans[ approachType ][ self.approachNumber ];
animDist = length( anim.coverTransDist[ approachType ][ self.approachNumber ] );
requiredDistSq = animDist + allowedError;
requiredDistSq = requiredDistSq * requiredDistSq;
// we should already be close
while ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) > requiredDistSq )
wait .05;
if ( isdefined( self.arrivalStartDist ) && self.arrivalStartDist < animDist + allowedError )
{
/# debug_arrival( "Aborting exposed approach because cover arrival dist is shorter" ); #/
return;
}
if ( !self exposedApproachConditionCheck( node, goalMatchesNode ) )
return;
dist = distance( self.origin, self.pathGoalPos );
if ( abs( dist - animDist ) > allowedError )
{
/# debug_arrival( "Aborting exposed approach because distance difference exceeded allowed error: " + dist + " more than " + allowedError + " from " + animDist ); #/
return;
}
facingYaw = vectorToYaw( self.pathGoalPos - self.origin );
if ( isdefined( self.heat ) && goalMatchesNode )
{
requiredYaw = desiredFacingYaw - anim.coverTransAngles[ approachType ][ self.approachNumber ];
idealStartPos = getArrivalStartPos( self.pathGoalPos, desiredFacingYaw, approachtype, self.approachNumber );
}
else if ( animDist > 0 )
{
delta = anim.coverTransDist[ approachType ][ self.approachNumber ];
assert( delta[ 0 ] != 0 );
yawToMakeDeltaMatchUp = atan( delta[ 1 ] / delta[ 0 ] );
if ( !isdefined( self.faceEnemyArrival ) || self.faceMotion )
{
requiredYaw = facingYaw - yawToMakeDeltaMatchUp;
if ( AbsAngleClamp180( requiredYaw - self.angles[ 1 ] ) > 30 )
{
/# debug_arrival( "Aborting exposed approach because angle change was too great" ); #/
return;
}
}
else
{
requiredYaw = self.angles[1];
}
closerDist = dist - animDist;
idealStartPos = self.origin + vector_multiply( vectorNormalize( self.pathGoalPos - self.origin ), closerDist );
}
else
{
requiredYaw = self.angles[1];
idealStartPos = self.origin;
}
self startcoverarrival( idealStartPos, requiredYaw );
}
waitForPathGoalPos()
{
while ( 1 )
{
if ( isdefined( self.pathgoalpos ) )
return;
wait 0.1;
}
}
startMoveTransitionPreConditions()
{
// if we don't know where we're going, we can't check if it's a good idea to do the exit animation
// (and it's probably not)
if ( !isdefined( self.pathGoalPos ) )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.pathGoalPos is undefined" ); #/
return false;
}
if ( !self shouldFaceMotion() )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.faceMotion is false" ); #/
return false;
}
if ( self.a.pose == "prone" )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.a.pose is \"prone\"" ); #/
return false;
}
if ( isdefined( self.disableExits ) && self.disableExits )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): self.disableExits is true" ); #/
return false;
}
if ( self.stairsState != "none" )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): on stairs" ); #/
return false;
}
if ( !self isStanceAllowed( "stand" ) && !isdefined( self.heat ) )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): not allowed to stand" ); #/
return false;
}
if ( distanceSquared( self.origin, self.pathGoalPos ) < 10000 )
{
/# debug_arrival( "not exiting cover (ent " + self getentnum() + "): too close to goal" ); #/
return false;
}
return true;
}
startMoveTransitionConditions( exittype, exitNode )
{
if ( !isdefined( exittype ) )
{
/# debug_arrival( "aborting exit: not supported for node type " + exitNode.type ); #/
return false;
}
// since we transition directly into a standing run anyway,
// we might as well just use the standing exits when crouching too
if ( exittype == "exposed" || isdefined( self.heat ) )
{
if ( self.a.pose != "stand" && self.a.pose != "crouch" )
{
/# debug_arrival( "exposed exit aborted because anim_pose is not \"stand\" or \"crouch\"" ); #/
return false;
}
if ( self.a.movement != "stop" )
{
/# debug_arrival( "exposed exit aborted because anim_movement is not \"stop\"" ); #/
return false;
}
}
// don't do an exit away from an enemy that we would otherwise face as we moved away from them
if ( !isdefined( self.heat ) && isdefined( self.enemy ) && vectorDot( self.lookaheaddir, self.enemy.origin - self.origin ) < 0 )
{
if ( self canSeeEnemyFromExposed() && distanceSquared( self.origin, self.enemy.origin ) < 300 * 300 )
{
/# debug_arrival( "aborting exit: don't want to turn back to nearby enemy" ); #/
return false;
}
}
return true;
}
/#
startMoveTransition_debugInfo( exittype, exityaw )
{
if ( debug_arrivals_on_actor() )
{
println( "^3exiting cover (ent " + self getentnum() + ", type \"" + exittype + "\"):" );
println( " lookaheaddir = (" + self.lookaheaddir[ 0 ] + ", " + self.lookaheaddir[ 1 ] + ", " + self.lookaheaddir[ 2 ] + ")" );
angle = AngleClamp180( vectortoyaw( self.lookaheaddir ) - exityaw );
if ( angle < 0 )
println( " (Angle of " + ( 0 - angle ) + " right from node forward.)" );
else
println( " (Angle of " + angle + " left from node forward.)" );
}
}
#/
getExitNode()
{
exitNode = undefined;
if ( !isdefined( self.heat ) )
limit = 400; // 20 * 20
else
limit = 4096; // 64 * 64
if ( isdefined( self.node ) && ( distanceSquared( self.origin, self.node.origin ) < limit ) )
exitNode = self.node;
else if ( isdefined( self.prevNode ) && ( distanceSquared( self.origin, self.prevNode.origin ) < limit ) )
exitNode = self.prevNode;
if ( isdefined( exitNode ) && isdefined( self.heat ) && AbsAngleClamp180( self.angles[1] - exitNode.angles[1] ) > 30 )
return undefined;
return exitNode;
}
customMoveTransitionFunc()
{
if ( !isdefined( self.startMoveTransitionAnim ) )
return;
self animmode( "zonly_physics", false );
self orientmode( "face current" );
self SetFlaggedAnimKnobAllRestart( "move", self.startMoveTransitionAnim, %root, 1 );
if ( animHasNotetrack( self.startMoveTransitionAnim, "code_move" ) )
{
self animscripts\shared::DoNoteTracks( "move" ); // return on code_move
self OrientMode( "face motion" ); // want to face motion since we are only playing exit animation( no l / r / b animations )
self animmode( "none", false );
}
self animscripts\shared::DoNoteTracks( "move" );
}
determineNonNodeExitType( exittype )
{
if ( self.a.pose == "stand" )
exittype = "exposed";
else
exittype = "exposed_crouch";
if ( shouldCQB() )
exittype = exittype + "_cqb";
else if ( isdefined( self.heat ) )
exittype = "heat";
return exittype;
}
determineHeatCoverExitType( exitNode, exittype )
{
if ( exitNode.type == "Cover Right" )
exittype = "heat_right";
else if ( exitNode.type == "Cover Left" )
exittype = "heat_left";
return exittype;
}
startMoveTransition()
{
if ( isdefined( self.customMoveTransition ) )
{
customTransition = self.customMoveTransition;
if ( !isdefined( self.permanentCustomMoveTransition ) )
self.customMoveTransition = undefined;
[[ customTransition ]]();
if ( !isdefined( self.permanentCustomMoveTransition ) )
self.startMoveTransitionAnim = undefined;
self clearanim( %root, 0.2 );
self orientmode( "face default" );
self animmode( "none", false );
return;
}
self endon( "killanimscript" );
if ( !self startMoveTransitionPreConditions() )
return;
// assume an exit from exposed.
exitpos = self.origin;
exityaw = self.angles[ 1 ];
exittype = "exposed";
exitTypeFromNode = false;
exitNode = getExitNode();
// if we're at a node, try to do an exit from the node.
if ( isdefined( exitNode ) )
{
nodeExitType = determineNodeExitType( exitNode );
if ( isdefined( nodeExitType ) )
{
exitType = nodeExitType;
exitTypeFromNode = true;
if ( isdefined( self.heat ) )
exitType = determineHeatCoverExitType( exitNode, exittype );
if ( !isdefined( anim.exposedTransition[ exitType ] ) && exittype != "stand_saw" && exittype != "crouch_saw" )
{
// if angle is wrong, don't do exit behavior for the node. Distance check already done in getExitNode
anglediff = AbsAngleClamp180( self.angles[ 1 ] - GetNodeForwardYaw( exitNode ) );
if ( anglediff < 5 )
{
// do exit behavior for the node.
if ( !isdefined( self.heat ) )
exitpos = exitNode.origin;
exityaw = GetNodeForwardYaw( exitNode );
}
}
}
}
/# self startMoveTransition_debugInfo( exittype, exityaw ); #/
if ( !self startMoveTransitionConditions( exittype, exitNode ) )
return;
isExposedExit = isdefined( anim.exposedTransition[ exittype ] );
if ( !exitTypeFromNode )
exittype = determineNonNodeExitType();
// since we're leaving, take the opposite direction of lookahead
leaveDir = ( -1 * self.lookaheaddir[ 0 ], -1 * self.lookaheaddir[ 1 ], 0 );
result = getMaxDirectionsAndExcludeDirFromApproachType( exitNode );
maxDirections = result.maxDirections;
excludeDir = result.excludeDir;
angleDataObj = spawnstruct();
calculateNodeTransitionAngles( angleDataObj, exittype, false, exityaw, leaveDir, maxDirections, excludeDir );
sortNodeTransitionAngles( angleDataObj, maxDirections );
approachnumber = -1;
numAttempts = 3;
if ( isExposedExit )
numAttempts = 1;
for ( i = 1; i <= numAttempts; i++ )
{
assert( angleDataObj.transIndex[ i ] != excludeDir );// shouldn't hit excludeDir unless numAttempts is too big
approachNumber = angleDataObj.transIndex[ i ];
if ( self checkCoverExitPos( exitpos, exityaw, exittype, isExposedExit, approachNumber ) )
break;
/# debug_arrival( "exit blocked: dir " + approachNumber ); #/
}
if ( i > numAttempts )
{
/# debug_arrival( "aborting exit: too many exit directions blocked" ); #/
return;
}
// if AI is closer to destination than exitPos is, don't do exit
allowedDistSq = distanceSquared( self.origin, self.coverExitPos ) * 1.25 * 1.25;
if ( distanceSquared( self.origin, self.pathgoalpos ) < allowedDistSq )
{
/# debug_arrival( "exit failed, too close to destination" ); #/
return;
}
/# debug_arrival( "exit success: dir " + approachNumber ); #/
self doCoverExitAnimation( exittype, approachNumber );
}
str( val )
{
if ( !isdefined( val ) )
return "{undefined}";
return val;
}
doCoverExitAnimation( exittype, approachNumber )
{
assert( isdefined( approachNumber ) );
assert( approachnumber > 0 );
assert( isdefined( exittype ) );
leaveAnim = anim.coverExit[ exittype ][ approachnumber ];
assert( isdefined( leaveAnim ) );
lookaheadAngles = vectortoangles( self.lookaheaddir );
/#
if ( debug_arrivals_on_actor() )
{
endpos = self.origin + vector_multiply( self.lookaheaddir, 100 );
thread debugLine( self.origin, endpos, ( 1, 0, 0 ), 1.5 );
}
#/
if ( self.a.pose == "prone" )
return;
transTime = 0.2;
self animMode( "zonly_physics", false );
self OrientMode( "face angle", self.angles[ 1 ] );
self setFlaggedAnimKnobAllRestart( "coverexit", leaveAnim, %body, 1, transTime, self.moveTransitionRate );
assert( animHasNotetrack( leaveAnim, "code_move" ) );
self animscripts\shared::DoNoteTracks( "coverexit" ); // until "code_move"
self.a.pose = "stand";
self.a.movement = "run";
self.ignorePathChange = undefined;
self OrientMode( "face motion" ); // want to face motion since we are only playing exit animation( no l / r / b animations )
self animmode( "none", false );
self finishCoverExitNotetracks( "coverexit" );
// need to clear everything above leaveAnim
//self clearanim( leaveAnim, 0.2 );
self clearanim( %root, 0.2 );
self OrientMode( "face default" );
//self thread faceEnemyOrMotionAfterABit();
self animMode( "normal", false );
}
finishCoverExitNotetracks( flagname )
{
self endon( "move_loop_restart" );
self animscripts\shared::DoNoteTracks( flagname );
}
/*faceEnemyOrMotionAfterABit()
{
self endon( "killanimscript" );
self endon( "move_interrupt" );
wait 1.0;
// don't want to spin around if we're almost where we're going anyway
while ( isdefined( self.pathGoalPos ) && distanceSquared( self.origin, self.pathGoalPos ) < 200 * 200 )
wait .25;
self OrientMode( "face default" );
}*/
drawVec( start, end, duration, color )
{
for ( i = 0; i < duration * 100; i++ )
{
line( start + ( 0, 0, 30 ), end + ( 0, 0, 30 ), color );
wait 0.05;
}
}
drawApproachVec( approach_dir )
{
self endon( "killanimscript" );
for ( ;; )
{
if ( !isdefined( self.node ) )
break;
line( self.node.origin + ( 0, 0, 20 ), ( self.node.origin - vector_multiply( approach_dir, 64 ) ) + ( 0, 0, 20 ) );
wait( 0.05 );
}
}
calculateNodeTransitionAngles( angleDataObj, approachtype, isarrival, arrivalYaw, approach_dir, maxDirections, excludeDir )
{
angleDataObj.transitions = [];
angleDataObj.transIndex = [];
anglearray = undefined;
sign = 1;
offset = 0;
if ( isarrival )
{
anglearray = anim.coverTransAngles[ approachtype ];
sign = -1;
offset = 0;
}
else
{
anglearray = anim.coverExitAngles[ approachtype ];
sign = 1;
offset = 180;
}
for ( i = 1; i <= maxDirections; i++ )
{
angleDataObj.transIndex[ i ] = i;
if ( i == 5 || i == excludeDir || !isdefined( anglearray[ i ] ) )
{
angleDataObj.transitions[ i ] = -1.0003; // cos180 - epsilon
continue;
}
angles = ( 0, arrivalYaw + sign * anglearray[ i ] + offset, 0 );
dir = vectornormalize( anglestoforward( angles ) );
angleDataObj.transitions[ i ] = vectordot( approach_dir, dir );
}
}
/#
printdebug( pos, offset, text, color, linecolor )
{
for ( i = 0; i < 20 * 5; i++ )
{
line( pos, pos + offset, linecolor );
print3d( pos + offset, text, ( color, color, color ) );
wait .05;
}
}
#/
sortNodeTransitionAngles( angleDataObj, maxDirections )
{
for ( i = 2; i <= maxDirections; i++ )
{
currentValue = angleDataObj.transitions[ angleDataObj.transIndex[ i ] ];
currentIndex = angleDataObj.transIndex[ i ];
for ( j = i - 1; j >= 1; j -- )
{
if ( currentValue < angleDataObj.transitions[ angleDataObj.transIndex[ j ] ] )
break;
angleDataObj.transIndex[ j + 1 ] = angleDataObj.transIndex[ j ];
}
angleDataObj.transIndex[ j + 1 ] = currentIndex;
}
}
checkCoverExitPos( exitpoint, exityaw, exittype, isExposedExit, approachNumber )
{
angle = ( 0, exityaw, 0 );
forwardDir = anglestoforward( angle );
rightDir = anglestoright( angle );
forward = vector_multiply( forwardDir, anim.coverExitDist[ exittype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, anim.coverExitDist[ exittype ][ approachNumber ][ 1 ] );
exitPos = exitpoint + forward - right;
self.coverExitPos = exitPos;
/#
if ( debug_arrivals_on_actor() )
thread debugLine( self.origin, exitpos, ( 1, .5, .5 ), 1.5 );
#/
if ( !isExposedExit && !( self checkCoverExitPosWithPath( exitPos ) ) )
{
/#
debug_arrival( "cover exit " + approachNumber + " path check failed" );
#/
return false;
}
if ( !( self maymovefrompointtopoint( self.origin, exitPos ) ) )
return false;
if ( approachNumber <= 6 || isExposedExit )
return true;
// if 7, 8, 9 direction, split up check into two parts of the 90 degree turn around corner
// (already did the first part, from node to corner, now doing from corner to end of exit anim)
forward = vector_multiply( forwardDir, anim.coverExitPostDist[ exittype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, anim.coverExitPostDist[ exittype ][ approachNumber ][ 1 ] );
finalExitPos = exitPos + forward - right;
self.coverExitPos = finalExitPos;
/#
if ( debug_arrivals_on_actor() )
thread debugLine( exitpos, finalExitPos, ( 1, .5, .5 ), 1.5 );
#/
return( self maymovefrompointtopoint( exitPos, finalExitPos ) );
}
// don't want to pass in anim.coverTransDist or coverTransPreDist as paramter, since it will be copied
getArrivalStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber )
{
angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 );
forwardDir = anglestoforward( angle );
rightDir = anglestoright( angle );
forward = vector_multiply( forwardDir, anim.coverTransDist[ approachtype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, anim.coverTransDist[ approachtype ][ approachNumber ][ 1 ] );
return arrivalpoint - forward + right;
}
getArrivalPreStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber )
{
angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 );
forwardDir = anglestoforward( angle );
rightDir = anglestoright( angle );
forward = vector_multiply( forwardDir, anim.coverTransPreDist[ approachtype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, anim.coverTransPreDist[ approachtype ][ approachNumber ][ 1 ] );
return arrivalpoint - forward + right;
}
checkCoverEnterPos( arrivalpoint, arrivalYaw, approachtype, approachNumber, arrivalFromFront )
{
enterPos = getArrivalStartPos( arrivalPoint, arrivalYaw, approachType, approachNumber );
self.coverEnterPos = enterPos;
/#
if ( debug_arrivals_on_actor() )
thread debugLine( enterPos, arrivalpoint, ( 1, .5, .5 ), 1.5 );
#/
if ( level.newArrivals && approachNumber <= 6 && arrivalFromFront )
return true;
if ( !( self maymovefrompointtopoint( enterPos, arrivalpoint ) ) )
return false;
if ( approachNumber <= 6 || isdefined( anim.exposedTransition[ approachtype ] ) )
return true;
// if 7, 8, 9 direction, split up check into two parts of the 90 degree turn around corner
// (already did the second part, from corner to node, now doing from start of enter anim to corner)
originalEnterPos = getArrivalPreStartPos( enterPos, arrivalYaw, approachType, approachNumber );
self.coverEnterPos = originalEnterPos;
/#
if ( debug_arrivals_on_actor() )
thread debugLine( originalEnterPos, enterPos, ( 1, .5, .5 ), 1.5 );
#/
return( self maymovefrompointtopoint( originalEnterPos, enterPos ) );
}
debug_arrivals_on_actor()
{
/#
dvar = getdebugdvar( "debug_arrivals" );
if ( dvar == "off" )
return false;
if ( dvar == "on" )
return true;
if ( int( dvar ) == self getentnum() )
return true;
#/
return false;
}
debug_arrival( msg )
{
if ( !debug_arrivals_on_actor() )
return;
println( msg );
}