nx1-gsc-dump/animscripts/cover_arrival.gsc

2460 lines
64 KiB
Plaintext

#include animscripts\SetPoseMovement;
#include animscripts\combat_utility;
#include animscripts\utility;
#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 ];
if ( use_custom_coverset( self.approachtype ) )
{
arrivalAnim = self.customCoverEnterTrans[ self.approachtype ][ approachnumber ];
}
assert( isdefined( arrivalAnim ) );
if ( !isdefined( self.heat ) )
self thread abortApproachIfThreatened();
// tagJW<NOTE>: Use the blend_finish tag to determine blend time on the arrival animation
blend_time = 0.2;
if( self IsCQBWalking() )
{
blend_finish_time = GetNotetrackTimes( arrivalAnim, "cqb_blend_finish" );
}
else
{
blend_finish_time = GetNotetrackTimes( arrivalAnim, "blend_finish" );
}
if ( blend_finish_time.size > 0 )
{
blend_time = blend_finish_time[0] * GetAnimLength( arrivalAnim );
}
self clearanim( %body, blend_time );
self setFlaggedAnimRestart( "coverArrival", arrivalAnim, 1, blend_time, 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 animscripts\init_common::set_animarray_standing();
}
else if ( self.a.pose == "crouch" )
{
self animscripts\init_common::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" || approachtype == "left_cqb" || approachtype == "left_crouch_cqb" )
return 90.0;
else if ( approachtype == "right" || approachtype == "right_crouch" || approachtype == "right_cqb" || approachtype == "right_crouch_cqb" )
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 ); #/
self notify( "approach_aborted" );
return;
}
approachNumber = result.approachNumber;
/# debug_arrival( "approach success: dir " + approachNumber ); #/
if ( level._newArrivals && approachNumber <= 6 && arrivalFromFront )
{
self endon( "goal_changed" );
if (use_custom_coverset( approachtype ) )
{
self.arrivalStartDist = self.customCoverTransLongestDist[ approachtype ];
}
else
{
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 );
if (use_custom_coverset( approachtype ) )
{
self.arrivalStartDist = length( self.customCoverEnterDist[ approachtype ][ approachNumber ] );
}
else
{
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;
self notify( "approach_aborted" );
return;
}
if ( result.approachNumber < 0 )
{
/# debug_arrival( "final approach aborted: " + result.failure ); #/
self.arrivalStartDist = undefined;
self notify( "approach_aborted" );
return;
}
approachNumber = result.approachNumber;
/# debug_arrival( "final approach success: dir " + approachNumber ); #/
requiredYaw = approachFinalYaw - get_approach_yaw( approachType, approachNumber );
}
else
{
// set arrival position and wait
self setRunToPos( self.coverEnterPos );
self waittill( "runto_arrived" );
requiredYaw = approachFinalYaw - get_approach_yaw( approachType, approachNumber );
if ( !self coverApproachLastMinuteCheck( approachPoint, approachFinalYaw, approachType, approachNumber, requiredYaw ) )
{
self notify( "approach_aborted" );
return;
}
}
self.approachNumber = approachNumber; // used in cover_arrival::main()
self.approachType = approachType;
self.arrivalStartDist = undefined;
self startcoverarrival( self.coverEnterPos, requiredYaw );
}
get_approach_yaw( approachType, approachNumber )
{
if( use_custom_coverset( approachtype ) )
{
requiredYaw = self.customCoverEnterAngles[ approachType ][ approachNumber ];
}
else
{
requiredYaw = anim.coverTransAngles[ approachType ][ approachNumber ];
}
return 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 );
if( use_custom_coverset( approachtype ) )
{
requiredYaw = approachYaw - self.customCoverEnterAngles[ approachType ][ resultobj.approachNumber ];
}
else
{
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 - checkDist ) / 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 ];
if ( use_custom_coverset( approachtype ) )
{
animDist = length( self.customCoverEnterDist[ approachtype ][ self.approachNumber ] );
}
else
{
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 - get_approach_yaw( approachType, self.approachNumber );
idealStartPos = getArrivalStartPos( self.pathGoalPos, desiredFacingYaw, approachtype, self.approachNumber );
}
else if ( animDist > 0 )
{
if ( use_custom_coverset( approachtype ) )
{
delta = self.customCoverEnterDist[ approachtype ][ self.approachNumber ];
}
else
{
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;
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 ];
if ( IsDefined( self.customCoverExitTrans ) && IsDefined( self.customCoverExitTrans[ exittype ] ) && IsDefined( self.customCoverExitTrans[ exittype ][ approachnumber ] ) )
{
leaveAnim = self.customCoverExitTrans[ 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;
// tagJW<NOTE>: Use the blend_finish tag to determine blend time on the arrival animation
transTime = 0.2;
blend_out_time = 0.2;
self.cqb_blend_finish_time = undefined;
self.run_blend_finish_time = undefined;
if ( animHasNotetrack( leaveAnim, "blend_finish" ) && !(self IsCQBWalking()) )
{
finish_time = GetNoteTrackTimes( leaveAnim, "finish" );
blend_finish_time = GetNotetrackTimes( leaveAnim, "blend_finish" );
/#
if ( blend_finish_time[0] < finish_time[0] )
{
AssertMsg( "blend_finish tag must occur later than finish tag." );
}
#/
assert( blend_finish_time[0] > finish_time[0] );
self.run_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( leaveAnim );
blend_out_time = self.run_blend_finish_time;
}
else if ( animHasNotetrack( leaveAnim, "cqb_blend_finish" ) && (self IsCQBWalking()) )
{
finish_time = GetNoteTrackTimes( leaveAnim, "finish" );
blend_finish_time = GetNotetrackTimes( leaveAnim, "cqb_blend_finish" );
/#
if ( blend_finish_time[0] < finish_time[0] )
{
AssertMsg( "blend_finish tag must occur later than finish tag." );
}
#/
assert( blend_finish_time[0] > finish_time[0] );
self.cqb_blend_finish_time = (blend_finish_time[0] - finish_time[0]) * GetAnimLength( leaveAnim );
blend_out_time = self.cqb_blend_finish_time;
}
// tagJW<NOTE>: Check to see if a run anim start has been defined for this anim
self animscripts\utility::handle_move_transition_notes( leaveAnim );
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, blend_out_time );
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 )
{
if( use_custom_coverset( approachtype ) )
{
anglearray = self.customCoverEnterAngles[ approachtype ];
}
else
{
anglearray = anim.coverTransAngles[ approachtype ];
}
sign = -1;
offset = 0;
}
else
{
if( use_custom_coverset( approachtype ) )
{
anglearray = self.customCoverExitAngles[ approachtype ];
}
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 );
if( use_custom_coverset( exittype ) )
{
forward = vector_multiply( forwardDir, self.customCoverExitDist[ exittype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, self.customCoverExitDist[ exittype ][ approachNumber ][ 1 ] );
}
else
{
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)
if( use_custom_coverset( exittype ) )
{
forward = vector_multiply( forwardDir, self.customCoverExitPostDist[ exittype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, self.customCoverExitPostDist[ exittype ][ approachNumber ][ 1 ] );
}
else
{
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 )
{
if( use_custom_coverset( approachtype ) )
{
angle = ( 0, arrivalYaw - self.customCoverEnterAngles[ approachtype ][ approachNumber ], 0 );
}
else
{
angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 );
}
forwardDir = anglestoforward( angle );
rightDir = anglestoright( angle );
if ( use_custom_coverset( approachtype ) )
{
animDist = length( self.customCoverEnterDist[ approachtype ][ approachNumber ] );
forward = vector_multiply( forwardDir, self.customCoverEnterDist[ approachtype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, self.customCoverEnterDist[ approachtype ][ approachNumber ][ 1 ] );
}
else
{
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 )
{
if( use_custom_coverset( approachtype ) )
{
angle = ( 0, arrivalYaw - self.customCoverEnterAngles[ approachtype ][ approachNumber ], 0 );
}
else
{
angle = ( 0, arrivalYaw - anim.coverTransAngles[ approachtype ][ approachNumber ], 0 );
}
forwardDir = anglestoforward( angle );
rightDir = anglestoright( angle );
if(use_custom_coverset( approachtype ))
{
forward = vector_multiply( forwardDir, self.customCoverTransPreDist[ approachtype ][ approachNumber ][ 0 ] );
right = vector_multiply( rightDir, self.customCoverTransPreDist[ approachtype ][ approachNumber ][ 1 ] );
}
else
{
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 ) );
}
use_custom_coverset( approachtype )
{
if ( IsDefined( self.customCoverEnterTrans ) && IsDefined( self.customCoverEnterTrans[ approachtype ] ) )
{
return true;
}
else
{
return false;
}
}
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;
if ( IsDefined( self.name ) )
{
msg = self.name + ": " + msg;
}
println( msg );
}
// --------------------------------------------------------------------------------
// ---- Cover Arrival/Exit Tool ----
// --------------------------------------------------------------------------------
/#
fakeAiLogic()
{
self.animType = "default";
self.a.script = "move";
self.a.pose = "stand";
self.a.prevPose = "stand";
self.weapon = "ak47_sp";
self.ignoreMe = true;
self.ignoreAll = true;
self AnimMode("nogravity");
self ForceTeleport( level._player.origin + (0,0,-1000), self.origin );
while( IsDefined(self) && IsAlive(self) )
{
wait(0.05);
}
}
coverArrivalDebugTool()
{
if ( isdefined( level.cover_arrival_debug_tool ) )
{
return;
}
level.cover_arrival_debug_tool = 1;
wait 1;
// same as nodeColorTable in pathnode.cpp
nodeColors = [];
nodeColors["Cover Stand"] = (0, 0.54, 0.66);
nodeColors["Cover Crouch"] = (0, 0.93, 0.72);
nodeColors["Cover Crouch Window"] = (0, 0.7, 0.5);
nodeColors["Cover Prone"] = (0, 0.6, 0.46);
nodeColors["Cover Right"] = (0.85, 0.85, 0.1);
nodeColors["Cover Left"] = (1, 0.7, 0);
nodeColors["Cover Wide Right"] = (0.75, 0.75, 0);
nodeColors["Cover Wide Left"] = (0.75, 0.53, 0.38);
nodeColors["Conceal Stand"] = (0, 0, 1);
nodeColors["Conceal Crouch"] = (0, 0, 0.75);
nodeColors["Conceal Prone"] = (0, 0, 0.5);
nodeColors["Turret"] = (0, 0.93, 0.72);
nodeColors["Bad"] = (1, 0, 0);
nodeColors["Poor"] = (1,0.5,0);
nodeColors["Ok"] = (1, 1, 0);
nodeColors["Good"] = (0, 1, 0);
//wait_for_first_player();
player = level._player;
// same as PREDICTION_TRACE_MIN + AIPHYS_STEPSIZE and PREDICTION_TRACE_MAX in actor_navigation.cpp
traceMin = (-15,-15,18);
traceMax = (15,15,72);
hudGood = newHudElem();
hudGood.location = 0;
hudGood.alignX = "left";
hudGood.alignY = "middle";
hudGood.foreground = 1;
hudGood.fontScale = 1.3;
hudGood.sort = 20;
hudGood.x = 680;
hudGood.y = 240;
hudGood.og_scale = 1;
hudGood.color = nodeColors["Good"];
hudGood.alpha = 1;
hudOk = newHudElem();
hudOk.location = 0;
hudOk.alignX = "left";
hudOk.alignY = "middle";
hudOk.foreground = 1;
hudOk.fontScale = 1.3;
hudOk.sort = 20;
hudOk.x = 680;
hudOk.y = 260;
hudOk.og_scale = 1;
hudOk.color = nodeColors["Ok"];
hudOk.alpha = 1;
hudPoor = newHudElem();
hudPoor.location = 0;
hudPoor.alignX = "left";
hudPoor.alignY = "middle";
hudPoor.foreground = 1;
hudPoor.fontScale = 1.3;
hudPoor.sort = 20;
hudPoor.x = 680;
hudPoor.y = 280;
hudPoor.og_scale = 1;
hudPoor.color = nodeColors["Poor"];
hudPoor.alpha = 1;
hudBad = newHudElem();
hudBad.location = 0;
hudBad.alignX = "left";
hudBad.alignY = "middle";
hudBad.foreground = 1;
hudBad.fontScale = 1.3;
hudBad.sort = 20;
hudBad.x = 680;
hudBad.y = 300;
hudBad.og_scale = 1;
hudBad.color = nodeColors["Bad"];
hudBad.alpha = 1;
hudTotal = newHudElem();
hudTotal.location = 0;
hudTotal.alignX = "left";
hudTotal.alignY = "middle";
hudTotal.foreground = 1;
hudTotal.fontScale = 1.3;
hudTotal.sort = 20;
hudTotal.x = 680;
hudTotal.y = 330;
hudTotal.og_scale = 1;
hudTotal.color = (1,1,1);
hudTotal.alpha = 1;
hudGoodText = newHudElem();
hudGoodText.location = 0;
hudGoodText.alignX = "right";
hudGoodText.alignY = "middle";
hudGoodText.foreground = 1;
hudGoodText.fontScale = 1.3;
hudGoodText.sort = 20;
hudGoodText.x = 670;
hudGoodText.y = 240;
hudGoodText.og_scale = 1;
hudGoodText.color = nodeColors["Good"];
hudGoodText.alpha = 1;
hudGoodText SetText("Good: ");
hudOkText = newHudElem();
hudOkText.location = 0;
hudOkText.alignX = "right";
hudOkText.alignY = "middle";
hudOkText.foreground = 1;
hudOkText.fontScale = 1.3;
hudOkText.sort = 20;
hudOkText.x = 670;
hudOkText.y = 260;
hudOkText.og_scale = 1;
hudOkText.color = nodeColors["Ok"];
hudOkText.alpha = 1;
hudOkText SetText("Ok: ");
hudPoorText = newHudElem();
hudPoorText.location = 0;
hudPoorText.alignX = "right";
hudPoorText.alignY = "middle";
hudPoorText.foreground = 1;
hudPoorText.fontScale = 1.3;
hudPoorText.sort = 20;
hudPoorText.x = 670;
hudPoorText.y = 280;
hudPoorText.og_scale = 1;
hudPoorText.color = nodeColors["Poor"];
hudPoorText.alpha = 1;
hudPoorText SetText("Poor: ");
hudBadText = newHudElem();
hudBadText.location = 0;
hudBadText.alignX = "right";
hudBadText.alignY = "middle";
hudBadText.foreground = 1;
hudBadText.fontScale = 1.3;
hudBadText.sort = 20;
hudBadText.x = 670;
hudBadText.y = 300;
hudBadText.og_scale = 1;
hudBadText.color = nodeColors["Bad"];
hudBadText.alpha = 1;
hudBadText SetText("Bad: ");
hudLineText = newHudElem();
hudLineText.location = 0;
hudLineText.alignX = "left";
hudLineText.alignY = "middle";
hudLineText.foreground = 1;
hudLineText.fontScale = 1.3;
hudLineText.sort = 20;
hudLineText.x = 630;
hudLineText.y = 315;
hudLineText.og_scale = 1;
hudLineText.color = (1,1,1);
hudLineText.alpha = 1;
hudLineText SetText("------------------");
hudTotalText = newHudElem();
hudTotalText.location = 0;
hudTotalText.alignX = "right";
hudTotalText.alignY = "middle";
hudTotalText.foreground = 1;
hudTotalText.fontScale = 1.3;
hudTotalText.sort = 20;
hudTotalText.x = 670;
hudTotalText.y = 330;
hudTotalText.og_scale = 1;
hudTotalText.color = (1,1,1);
hudTotalText.alpha = 1;
hudTotalText SetText("Total: ");
badNode = undefined;
fakeAi = undefined;
while(1)
{
tool = getdvarint( "ai_debugCoverArrivalsTool");
// check if there's a node selected in Radiant
if( tool <= 0 && IsDefined(level.nodedrone) && IsDefined(level.nodedrone.currentnode) )
{
tool = 1;
}
if( tool <= 0 )
{
if( IsDefined(fakeAi) )
{
fakeAi Delete();
fakeAi = undefined;
}
// hide the UI
hudBad.alpha = 0;
hudPoor.alpha = 0;
hudOk.alpha = 0;
hudGood.alpha = 0;
hudTotal.alpha = 0;
hudBadText.alpha = 0;
hudPoorText.alpha = 0;
hudOkText.alpha = 0;
hudGoodText.alpha = 0;
hudLineText.alpha = 0;
hudTotalText.alpha = 0;
wait 0.2;
continue;
}
// spawn the fakeAi
if( !IsDefined(fakeAi) )
{
spawners = getSpawnerArray();
for( i=0; i < spawners.size; i++ )
{
fakeAi = spawners[i] StalingradSpawn();
if( IsDefined(fakeAi) )
{
spawners[0].count++;
fakeAi AnimCustom( ::fakeAiLogic );
break;
}
}
if( !IsDefined(fakeAi) )
{
//iprintlnbold("no suitable spawners found in level");
wait 0.2;
continue;
}
}
// show the UI
hudBad.alpha = 1;
hudPoor.alpha = 1;
hudOk.alpha = 1;
hudGood.alpha = 1;
hudTotal.alpha = 1;
hudBadText.alpha = 1;
hudPoorText.alpha = 1;
hudOkText.alpha = 1;
hudGoodText.alpha = 1;
hudLineText.alpha = 1;
hudTotalText.alpha = 1;
numBad = 0;
numPoor = 0;
numOk = 0;
numGood = 0;
tracesThisFrame = 0;
renderedThisFrame = 0;
evaluatedThisframe = 0;
// find all cover node within given radius
radius = getdvarfloat( "ai_debugCoverArrivalsToolRadius");
nodes = GetNodesInRadiusSorted( player.origin, radius, 0, 128, "Cover" );
// show the node selected in Radiant
if( tool > 0 && IsDefined(level.nodedrone) && IsDefined(level.nodedrone.currentnode) )
{
nodes = [];
nodes[0] = level.nodedrone.currentnode;
}
showNodes = getdvarint( "ai_debugCoverArrivalsToolShowNodes");
totalNodes = 1;
if( showNodes > 0 || nodes.size == 0 )
{
totalNodes = nodes.size;
}
fakeAi.cqbwalking = false;
fakeAi.weapon = "ak47_sp";
fakeAi.heat = false;
// set cqb/pistol
toolType = getdvarint( "ai_debugCoverArrivalsToolType");
if( toolType == 0 )
{
fakeAi.cqbwalking = false;
}
else if( toolType == 1 )
{
fakeAi.cqbwalking = true;
}
else if( toolType == 2 )
{
fakeAi.weapon = "m1911_sp";
}
// ALEXP_TODO: this is pretty useless.. need to do it off FPS
// scale based on number of AI (more AI -> lower FPS)
minAi = 5;
maxAi = 15;
nodesMin = 5;
nodesMax = 30;
allAi = GetAiArray(); //entsearch( level.CONTENTS_ACTOR, player.origin, 10000 );
numAi = allAi.size;
// clamp
if( numAi < minAi )
{
numAi = minAi;
}
else if( numAi > maxAi )
{
numAi = maxAi;
}
maxNodesPerFrame = (numAi - minAi) / (maxAi - minAi);
maxNodesPerFrame = (1-maxNodesPerFrame) * (nodesMax - nodesMin) + nodesMin;
// how often to evaluate nodes (spread them out for optimization)
frameInterval = int( ceil( totalNodes / maxNodesPerFrame ) );
//println("interval: " + frameInterval + " nodes: " + maxNodesPerFrame + " ai: " + numAi);
for( i=0; i < totalNodes && i < nodes.size; i++ )
{
node = nodes[i];
// clear the cached data
if( !IsDefined(node.tool) || node.tool != tool || !IsDefined(node.toolType) || node.toolType != toolType )
{
node.angleDeltaArray = undefined;
node.moveDeltaArray = undefined;
node.preMoveDeltaArray = undefined;
node.postMoveDeltaArray = undefined;
node.tool = tool;
node.toolType = toolType;
}
assert( IsDefined(node) );
if( !IsDefined(node) || node.type == "BAD NODE" || node.type == "Path" )
{
totalNodes++;
continue;
}
else if( !IsDefined( anim.approach_types[node.type] ) )
{
totalNodes++;
continue;
}
// skip node evaluation every few frames (PhysicsTrace is expensive)
if( IsDefined(node.lastCheckedTime) && (gettime() - node.lastCheckedTime) < 50*frameInterval )
{
if( node.lastRatio == 0 )
{
numBad++;
}
else if( node.lastRatio < 0.5 )
{
numPoor++;
}
else if( node.lastRatio < 1.0 )
{
numOk++;
}
else
{
numGood++;
}
continue;
}
// limit the number of traces per frame
if( evaluatedThisFrame > maxNodesPerFrame ) // 30 nodes
{
continue;
}
// use the the AI that's on the node if there is one
testAi = fakeAi;
/*nearAiArray = entsearch( level.CONTENTS_ACTOR, node.origin, 16 );
if( nearAiArray.size > 0 )
{
testAi = nearAiArray[0];
}*/
renderNode = true;
if( DistanceSquared(node.origin, player.origin) > 800*800 ) //|| renderedThisFrame*frameInterval > maxNodesPerFrame )
{
renderNode = false;
}
nodeColor = nodeColors["Good"];
// find transition type
//stance = node isNodeDontStand() && !node isNodeDontCrouch();
//transType = anim.approach_types[node.type][stance];
transType = fakeAi determineNodeApproachType( node );
totalTransitions = 0;
validTransitions = 0;
/*animName = "arrive_" + transType;
// exit support
if( tool == 2 )
{
animName = "exit_" + transType;
}
if( fakeAi shouldDoCQBTransition( node, transType ) )
animName = animName + "_cqb";*/
// cache for performance
if( !IsDefined(node.angleDeltaArray) )
{
if ( tool == 2 ) // Exit
{
node.angleDeltaArray = anim.coverExitAngles[ transType ];
}
else
{
node.angleDeltaArray = anim.coverTransAngles[ transType ];
}
}
// go through all available transitions
for( j=1; j <= 9; j++ )
{
if( IsDefined(node.angleDeltaArray[j]) )
{
totalTransitions++;
lineColor = (0.5,0,0);
approachIsGood = false;
originalEnterPos = undefined;
// final yaw at node
approachFinalYaw = node.angles[1] + animscripts\cover_arrival::getNodeStanceYawOffset( transType );
angle = (0, approachFinalYaw - node.angleDeltaArray[j], 0);
// exits
if( tool == 2 )
{
angle = (0, approachFinalYaw, 0 );
}
forwardDir = AnglesToForward( angle );
rightDir = AnglesToRight( angle );
// cache for performance
if( !IsDefined(node.moveDeltaArray) )
{
if ( tool == 2 )
{
node.moveDeltaArray = anim.coverExitDist[ transType ];
}
else
{
node.moveDeltaArray = anim.coverTransDist[ transType ];
}
}
enterPos = node.origin;
forward = vector_multiply( forwardDir, node.moveDeltaArray[j][0] );
right = vector_multiply( rightDir, node.moveDeltaArray[j][1] );
// start position of arrival animation
if( tool == 1 ) // arrival
{
enterPos = node.origin - forward + right;
}
else
{
enterPos = node.origin + forward - right;
}
// check if the AI can move between the node and anim start pos or corner
if( testAi MayMoveFromPointToPoint(node.origin, enterPos) )
{
approachIsGood = true;
lineColor = (0,0.75,0);
}
if( renderNode )
{
line( node.origin, enterPos, lineColor, 1, 1, frameInterval );
}
// 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)
if( approachIsGood && j >= 7 && !isSubstr(transType, "exposed") )
{
originalEnterPos = enterPos;
if( tool == 1 ) // arrival
{
if( !IsDefined(node.preMoveDeltaArray) )
{
node.preMoveDeltaArray = anim.coverTransPreDist[ transType ];//fakeAi preMoveDeltaArray( animName, "move" );
}
forward = vector_multiply( forwardDir, node.preMoveDeltaArray[j][0] );
right = vector_multiply( rightDir, node.preMoveDeltaArray[j][1] );
originalEnterPos = enterPos - forward + right;
}
else // exit
{
if( !IsDefined(node.postMoveDeltaArray) )
{
node.postMoveDeltaArray = anim.coverExitPostDist[ transType ];//fakeAi postMoveDeltaArray( animName, "move" );
}
forward = vector_multiply( forwardDir, node.postMoveDeltaArray[j][0] );
right = vector_multiply( rightDir, node.postMoveDeltaArray[j][1] );
originalEnterPos = enterPos + forward - right;
}
// check if the AI can move between the corner and anim start pos
if( !testAi MayMoveFromPointToPoint(originalEnterPos, enterPos) )
{
approachIsGood = false;
lineColor = (0.5,0,0);
}
if( renderNode )
{
line( originalEnterPos, enterPos, lineColor, 1, 1, frameInterval );
print3d( originalEnterPos, j, lineColor, 1, 0.2, frameInterval );
}
}
else if( renderNode )
{
print3d( enterPos, j, lineColor, 1, 0.2, frameInterval );
}
if( approachIsGood )
{
validTransitions++;
}
tracesThisFrame++;
}
}
// assign categories based on number of valid transitions
node.lastRatio = validTransitions / totalTransitions;
if( node.lastRatio == 0 )
{
nodeColor = nodeColors["Bad"];
numBad++;
badNode = node;
}
else if( node.lastRatio < 0.5 )
{
nodeColor = nodeColors["Poor"];
numPoor++;
}
else if( node.lastRatio < 1.0 )
{
nodeColor = nodeColors["Ok"];
numOk++;
}
else
{
numGood++;
}
if( renderNode )
{
// render box, node type and node forward
print3d( node.origin, node.type + " (" + transType + ")", nodeColor, 1, 0.35, frameInterval );
box( node.origin, 16, node.angles[1], nodeColor, 1, 1, frameInterval );
nodeForward = anglesToForward( node.angles );
nodeForward = vector_multiply( nodeForward, 8 );
line( node.origin, node.origin + nodeForward, nodeColor, 1, 1, frameInterval );
renderedThisFrame++;
}
// save last time
evaluatedThisFrame++;
node.lastCheckedTime = gettime();
}
//println("evaluated: " + evaluatedThisFrame + " traced: " + tracesThisFrame + " rendered: " + renderedThisFrame);
// render stats
hudTotal SetValue( numBad + numPoor + numOk + numGood );
hudBad SetValue( numBad );
hudPoor SetValue( numPoor );
hudOk SetValue( numOk );
hudGood SetValue( numGood );
wait(0.05);
}
}
#/