#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 ); }