#include maps\mp\agents\_scriptedAgents; main() { self endon( "killanimscript" ); self.bLockGoalPos = true; startNode = self GetNegotiationStartNode(); endNode = self GetNegotiationEndNode(); assert( IsDefined( startNode ) && IsDefined( endNode ) ); if ( startNode.type == "Jump" || startNode.type == "Jump Attack" ) { nextNode = self GetNegotiationNextNode(); if ( IsDefined( startNode.target ) && IsDefined( endNode.targetname ) && startNode.target == endNode.targetname ) { self.traverseType = "canned"; self DoTraverse( startNode, endNode ); return; } attackableEnemy = find_attackable_enemy_at_node( endNode ); if ( IsDefined( attackableEnemy ) ) { self.traverseType = "jump_attack"; self.leapEndPos = endNode.origin; self maps\mp\agents\alien\_alien_melee::melee_leap( attackableEnemy ); } else { self.traverseType = "jump"; self Jump( startNode, endNode, nextNode ); } } else { self.traverseType = "canned"; self doTraverse( startNode, endNode ); } } find_attackable_enemy_at_node( nodeToCheck ) { if (( self maps\mp\alien\_utility::get_alien_type() == "spitter" ) || ( self maps\mp\alien\_utility::get_alien_type() == "seeder" )) return undefined; CLOSE_PLAYER_DIST_SQ = 128 * 128; COS_45 = 0.707; foreach ( player in level.players ) { if ( DistanceSquared( player.origin, nodeToCheck.origin ) > CLOSE_PLAYER_DIST_SQ ) continue; playerToNode = VectorNormalize( nodeToCheck.origin - player.origin ); playerForward = AnglesToForward( player.angles ); forwardDot = VectorDot( playerToNode, playerForward ); if ( forwardDot > COS_45 ) return player; } return undefined; } end_script() { self.bLockGoalPos = false; if ( self.traverseType == "jump" ) { self.previousAnimState = "traverse_jump"; } else if ( self.traverseType == "jump_attack" ) { self.previousAnimState = "traverse_jump_attack"; } else { self.previousAnimState = "traverse_canned"; } self.traverseType = undefined; } Jump( startNode, endNode, nextNode ) { nextPos = undefined; if ( IsDefined( nextNode ) ) nextPos = nextNode.origin; if ( isDefined( level.dlc_alien_jump_override ) ) { [[level.dlc_alien_jump_override]]( startNode, endNode, nextNode, nextPos ); return; } self maps\mp\agents\alien\_alien_jump::Jump( startNode.origin, startNode.angles, endNode.origin, endNode.angles, nextPos, undefined, endNode.script_noteworthy ); } doTraverse( startNode, endNode ) { traverseData = level.alienAnimData.cannedTraverseAnims[ startNode.animscript ]; AssertEx( isDefined( traverseData ), "Traversal '" + startNode.animscript + "' is not supported" ); animState = traverseData [ "animState" ]; AssertEx( isDefined( animState ), "No animState specified for traversal '" + startNode.animscript + "'" ); self.startNode = startNode; self.endNode = endNode; self ScrAgentSetPhysicsMode( "noclip" ); self ScrAgentSetOrientMode( "face angle abs", startNode.angles ); self ScrAgentSetAnimMode( "anim deltas" ); self ScrAgentSetAnimScale( 1.0, 1.0 ); if ( isdefined( traverseData[ "traverseSound" ] ) ) self thread maps\mp\_utility::play_sound_on_tag( traverseData[ "traverseSound" ] ); if ( isdefined( traverseData[ "traverseAnimScale" ] ) ) self ScrAgentSetAnimScale( traverseData[ "traverseAnimScale" ], traverseData[ "traverseAnimScale" ] ); switch ( animState ) { case "traverse_climb_up": alienClimbUp( startNode, endNode, "traverse_climb_up", self GetAnimEntry( "traverse_climb_up", 4 ) ); break; case "traverse_climb_up_over_56": alienClimbUp( startNode, endNode, "traverse_climb_up_over_56" ); break; case "traverse_climb_up_ledge_18_run": alienClimbUp( startNode, endNode, "traverse_climb_up_ledge_18_run" ); break; case "traverse_climb_up_ledge_18_idle": alienClimbUp( startNode, endNode, "traverse_climb_up_ledge_18_idle" ); break; case "climb_up_end_jump_side_l": alienClimbUp( startNode, endNode, "climb_up_end_jump_side_l" ); break; case "climb_up_end_jump_side_r": alienClimbUp( startNode, endNode, "climb_up_end_jump_side_r" ); break; case "traverse_climb_down": alienClimbDown( startNode, endNode, "traverse_climb_down" ); break; case "traverse_climb_over_56_down": alienClimbDown( startNode, endNode, "traverse_climb_over_56_down" ); break; case "run": alienWallRun( startNode, endNode, "run" ); break; default: alienRegularTraversal( startNode, animState, traverseData [ "animIndexArray" ], traverseData [ "endInOriented" ], traverseData [ "flexHeightEndAtTraverseEnd" ] ); break; } self.startNode = undefined; self.endNode = undefined; self ScrAgentSetAnimScale( 1, 1 ); } alienRegularTraversal( startNode, animState, animIndexArray, endInOriented, flexHeightEndAtTraverseEnd ) { animIndex = animIndexArray [ RandomInt ( animIndexArray.size ) ]; animEntry = self GetAnimEntry( animState, animIndex ); result = needFlexibleHeightSupport( animEntry ); animTime = GetAnimLength( animEntry ); self traverseAnimLerp( animEntry, startNode ); // If we have an apex, move us away from our wall on death if ( AnimHasNotetrack( animEntry, "highest_point" ) ) self.apexTraversalDeathVector = VectorNormalize( self.startNode.origin - self.endNode.origin ); // If we are pointing to an entity, assume it's a scriptable scriptable = GetEnt( startnode.target, "targetname" ); if ( IsDefined( scriptable ) ) { scriptable thread runScriptableTraverse( animTime ); } if( result.need_support ) doTraversalWithFlexibleHeight( animState, animIndex, animEntry, result.start_notetrack, result.end_notetrack, flexHeightEndAtTraverseEnd, ::alienTraverseNotetrackHandler ); else PlayAnimNUntilNotetrack( animState, animIndex, "canned_traverse", "end", ::alienTraverseNotetrackHandler ); endRegularTraversal( endInOriented ); } runScriptableTraverse( animTime ) { self notify( "stop_previous_traversal" ); self endon( "stop_previous_traversal" ); self SetScriptablePartState( 0, 1 );//plays the animation wait animTime; self SetScriptablePartState( 0, 0 );//resets the scriptable state } endRegularTraversal( endInOriented ) { if( endInOriented ) { self ScrAgentSetPhysicsMode( "noclip" ); self.oriented = true; self.ignoreme = true; } else { self ScrAgentSetPhysicsMode( "gravity" ); self.oriented = false; self.ignoreme = false; } } needFlexibleHeightSupport( animEntry ) { result = spawnStruct(); if ( AnimHasNotetrack ( animEntry, "traverse_up" ) ) { result.need_support = true; result.start_notetrack = "traverse_up"; result.end_notetrack = "traverse_up_end"; return result; } if ( AnimHasNotetrack ( animEntry, "traverse_drop" ) ) { result.need_support = true; result.start_notetrack = "traverse_drop"; result.end_notetrack = "traverse_drop_end"; return result; } result.need_support = false; return result; } doTraversalWithFlexibleHeight( animState, animIndex, animEntry, startNotetrack, endNotetrack, flexHeightEndAtTraverseEnd, notetrackHandlerFunc ) { CONST_TRAVERSAL_ANIM_LABEL = "canned_traverse"; PlayAnimNUntilNotetrack( animState, animIndex, CONST_TRAVERSAL_ANIM_LABEL, startNotetrack, notetrackHandlerFunc ); if ( flexHeightEndAtTraverseEnd ) { flex_height_end_pos = self.endNode.origin; flex_height_anim_end_time = 1; } else { AssertEx( isDefined( self.endNode.target ), "Traversal " + animState + " " + animIndex + " at " + self.origin + ". Need to link a script struct from the traversal end point to mark the apex point for the animation" ); flex_height_end_pos = common_scripts\utility::getstruct( self.endNode.target, "targetname" ); AssertEx( isDefined( flex_height_end_pos ), "Traversal " + animState + " " + animIndex + " at " + self.origin + ". Unable to find the apex point struct" ); flex_height_end_pos = flex_height_end_pos.origin; apexNotetrackTimes = GetNotetrackTimes( animEntry, "highest_point" ); flex_height_anim_end_time = apexNotetrackTimes[ 0 ]; AssertEx( isDefined( flex_height_anim_end_time ), "Traversal " + animState + " " + animIndex + " at " + self.origin + ". Missing 'highest_point' notetrack" ); } doTraversalWithFlexibleHeight_internal( animState, animIndex, CONST_TRAVERSAL_ANIM_LABEL, animEntry, startNotetrack, endNotetrack, flex_height_end_pos, flex_height_anim_end_time, notetrackHandlerFunc ); } doTraversalWithFlexibleHeight_internal( animState, animIndex, animLabel, animEntry, startNotetrack, endNotetrack, flexHeightEndPos, flexHeightAnimEndTime, notetrackHandlerFunc ) { remaining_height = abs( self.origin[ 2 ] - flexHeightEndPos[ 2 ] ); startNotetrackTimes = GetNotetrackTimes( animEntry, startNotetrack ); start_time = startNotetrackTimes[ 0 ]; endNotetrackTimes = GetNotetrackTimes( animEntry, endNotetrack ); end_time = endNotetrackTimes[ 0 ]; AssertEx( end_time > start_time, "Traversal " + animState + " " + animIndex + " has incorrectly placed flexible height notetracks." ); remaining_anim_delta = GetMoveDelta( animEntry, start_time, flexHeightAnimEndTime ); remaining_anim_height = abs( remaining_anim_delta[ 2 ] ); anim_delta_between = GetMoveDelta( animEntry, start_time, end_time ); scaled_anim_height = abs( anim_delta_between[ 2 ] ); AssertEx( scaled_anim_height > 0.0, "Traversal " + animState + " " + animIndex + " has bad traverse notetracks." ); not_scaled_anim_height = remaining_anim_height - scaled_anim_height; // When we have time, we need to investigate why this is happening on certain traversals //AssertEx( ( remaining_height - not_scaled_anim_height ) > 0, "Traversal " + animState + " " + animIndex + " at " + self.origin + " has no vertical space to do flexible height." ); if ( remaining_height <= not_scaled_anim_height ) anim_scale = 1; else anim_scale = ( remaining_height - not_scaled_anim_height ) / scaled_anim_height; anim_rate = 1 / anim_scale; self ScrAgentSetAnimScale( 1.0, anim_scale ); PlayAnimNAtRateUntilNotetrack( animState, animIndex, anim_rate, animLabel, endNotetrack, notetrackHandlerFunc ); self ScrAgentSetAnimScale( 1.0, 1.0 ); PlayAnimNUntilNotetrack( animState, animIndex, animLabel, "end", notetrackHandlerFunc ); self.apexTraversalDeathVector = undefined; } alienTraverseNotetrackHandler( note, animState, animIndex, animTime ) { switch ( note ) { case "apply_physics": self ScrAgentSetPhysicsMode( "gravity" ); break; case "highest_point": if ( isDefined( self.apexTraversalDeathVector ) ) self.apexTraversalDeathVector *= -1; break; default: break; } } //=========================================== // Special traversals //=========================================== ////////////////// // Climb up alienClimbUp( startNode, endNode, animState, longerEndAnim ) { startAnim = self GetAnimEntry( animState, 0 ); scrabbleAnim = self GetAnimEntry( animState, 1 ); loopAnim = self GetAnimEntry( animState, 2 ); endAnim = self GetAnimEntry( animState, 3 ); totalHeight = endNode.origin[ 2 ] - startnode.origin[ 2 ]; startAnimHeight = GetMoveDelta( startAnim, 0, 1 )[ 2 ]; scrabbleAnimHeight = GetMoveDelta( scrabbleAnim, 0, 1 )[ 2 ]; loopAnimHeight = GetMoveDelta( loopAnim, 0, 1 )[ 2 ]; endAnimHeight = GetMoveDelta( endAnim, 0, 1 )[ 2 ]; longerEndAnimHeight = undefined; climbUpNotetrackTime = getNoteTrackTimes( startAnim, "climb_up_teleport" ) [ 0 ]; climbUpAnimDeltaBeforeNotetrack = GetMoveDelta( startAnim, 0, climbUpNotetrackTime ); climbUpAnimDeltaAfterNotetrack = GetMoveDelta( startAnim, climbUpNotetrackTime, 1 ); startAnimHeightAfterNotetrack = climbUpAnimDeltaAfterNotetrack[ 2 ]; if ( totalHeight < ( startAnimHeight + endAnimHeight ) ) Println( "ERROR: Height is too short for " + animState + ". Modify the geo or use another traversal." ); distForScrabbleAndLoop = totalHeight - ( startAnimHeight + endAnimHeight ); canDoScrabble = false; numOfLoop = 0; if ( distForScrabbleAndLoop > 0 ) { canDoScrabble = ( distForScrabbleAndLoop - scrabbleAnimHeight ) > 0; numOfLoop = max ( 0, floor ( ( distForScrabbleAndLoop - canDoScrabble * scrabbleAnimHeight ) / loopAnimHeight ) ); } teleportAnimHeight = canDoScrabble * scrabbleAnimHeight + numOfLoop * loopAnimHeight + startAnimHeightAfterNotetrack; teleportRealHeight = totalHeight - endAnimHeight - ( startAnimHeight - startAnimHeightAfterNotetrack ); animScalerZ = teleportRealHeight / teleportAnimHeight; canDoLongerEndAnim = false; if ( isDefined ( longerEndAnim )) { longerEndAnimHeight = GetMoveDelta( longerEndAnim, 0, 1 )[ 2 ]; endAnimHeightDiff = longerEndAnimHeight - endAnimHeight; canDoLongerEndAnim = ( teleportRealHeight - teleportAnimHeight ) > endAnimHeightDiff; animScalerZ = ( teleportRealHeight - canDoLongerEndAnim * endAnimHeightDiff )/ teleportAnimHeight; } selectedEndAnim = endAnim; if ( canDoLongerEndAnim ) selectedEndAnim = longerEndAnim; stopTeleportNotetrack = getNoteTrackTimes( selectedEndAnim, "stop_teleport" ) [ 0 ]; endAnimHeightBeforeNotetrack = GetMoveDelta( selectedEndAnim, 0, stopTeleportNotetrack )[ 2 ]; stopToEndAnimDelta = GetMoveDelta( selectedEndAnim, stopTeleportNotetrack, 1 ); stopToEndAnimDeltaXY = length( stopToEndAnimDelta * ( 1, 1, 0 ) ); // startAnim: Play the anim normally until climb_up_teleport notetrack self ScrAgentSetAnimScale( 1, 1 ); self traverseClimbUpLerp( startAnim, startNode ); PlayAnimNUntilNotetrack( animState, 0, "canned_traverse", "climb_up_teleport" ); // startAnim: Initial horizontal scaling to make up for any XY displacement. Start to scale to Z. self ScrAgentSetAnimScale( 1, animScalerZ ); self WaitUntilNotetrack( "canned_traverse", "end" ); // scrabble and loop animation: Continue the Z scaling. self ScrAgentSetAnimScale( 1, animScalerZ ); if ( canDoScrabble ) PlayAnimNUntilNotetrack( animState, 1, "canned_traverse", "finish" ); for ( i = 0; i < numOfLoop; i++ ) { PlayAnimNUntilNotetrack( animState, 2, "canned_traverse", "end" ); } //Final height adjustment, making sure alien reach enough height and will not end up inside geo when finish the traversal selfToEndHeight = endNode.origin[ 2 ] - self.origin[ 2 ] - stopToEndAnimDelta[ 2 ]; animScalerZ = 1.0; if ( selfToEndHeight > endAnimHeightBeforeNotetrack ) animScalerZ = selfToEndHeight / endAnimHeightBeforeNotetrack; self ScrAgentSetAnimScale( 1, animScalerZ ); if ( canDoLongerEndAnim ) PlayAnimNUntilNotetrack( animState, 4, "canned_traverse", "stop_teleport", ::alienTraverseNotetrackHandler ); else PlayAnimNUntilNotetrack( animState, 3, "canned_traverse", "stop_teleport", ::alienTraverseNotetrackHandler ); //Final horizontal adjustment, making sure alien will end at the traverse End node selfToEndXY = distance2D( self.origin, endNode.origin ); animScalerXY = selfToEndXY / stopToEndAnimDeltaXY; self ScrAgentSetAnimScale( animScalerXY, 1 ); self WaitUntilNotetrack( "canned_traverse", "end" ); } ///////////////////// // Climb down alienClimbDown( startNode, endNode, animState ) { startAnim = self GetAnimEntry( animState, 0 ); loopAnim = self GetAnimEntry( animState, 1 ); slideAnim = self GetAnimEntry( animState, 2 ); endAnim = self GetAnimEntry( animState, 3 ); jumpOffEndAnim = self GetAnimEntry( animState, 4 ); totalHeight= startNode.origin[ 2 ] - endNode.origin[ 2 ]; startAnimHeight = -1 * GetMoveDelta( startAnim, 0, 1 )[ 2 ]; slideAnimHeight = -1 * GetMoveDelta( slideAnim, 0, 1 )[ 2 ]; loopAnimHeight = -1 * GetMoveDelta( loopAnim, 0, 1 )[ 2 ]; endAnimHeight = -1 * GetMoveDelta( endAnim, 0, 1 )[ 2 ]; jumpOffEndAnimHeight = -1 * GetMoveDelta( jumpOffEndAnim, 0, 1 )[ 2 ]; if ( totalHeight < ( startAnimHeight + endAnimHeight ) ) Println( "ERROR: Height is too short for " + animState + ". Modify the geo or use another traversal." ); endAnimToPlay = endAnim; endAnimToPlayHeight = endAnimHeight; canDoJump = false; //Determine whether alien can play the jump off anim for end if ( self canDoJumpForEnd( startnode, endNode, startAnim, jumpOffEndAnim )) { endAnimToPlay = jumpOffEndAnim; endAnimToPlayHeight = jumpOffEndAnimHeight; canDoJump = true; } distForSlideAndLoop = totalHeight - ( startAnimHeight + endAnimToPlayHeight ); canDoSlide = false; numOfLoop = 0; if ( distForSlideAndLoop > 0 ) { canDoSlide = ( distForSlideAndLoop - slideAnimHeight ) > 0; numOfLoop = max ( 0, floor (( distForSlideAndLoop - canDoSlide * slideAnimHeight ) / loopAnimHeight )); } self ScrAgentSetAnimScale( 1, 1 ); self traverseClimbDownLerp( startAnim, startNode ); PlayAnimNUntilNotetrack( animState, 0, "canned_traverse", "end" ); slideAndLoopAnimHeight = canDoSlide * slideAnimHeight + numOfLoop * loopAnimHeight; if ( slideAndLoopAnimHeight > 0 ) { animScaler = abs( ( distForSlideAndLoop )/ slideAndLoopAnimHeight ); self ScrAgentSetAnimScale( 1, animScaler ); } //: Playing the loop and slide animation from the same anim state has caused the following issue.: // (1) The "will_finish_soon" notetrack will fire off immediately due to the short anim length for the slide anim, // causing the slide animation to not play // (2) When this happens, the alien will keep playing the loop animation even when the jump-off state is activated. // If time permits, we need to look into how situations like this should be prevented. for ( i = 0; i < numOfLoop; i++ ) { PlayAnimNUntilNotetrack( "traverse_climb_down_loop", 0, "traverse_climb_down_loop", "end" ); } if ( canDoSlide ) PlayAnimNUntilNotetrack( "traverse_climb_down_slide", 0, "traverse_climb_down_slide", "end" ); //Final height adjustment, making sure alien ends up on the ground when finish teleportStartTime = getNoteTrackTimes( endAnimToPlay, "climb_down_teleport" ) [ 0 ]; teleportEndTime = getNoteTrackTimes( endAnimToPlay, "stop_teleport" ) [ 0 ]; animHeightAfterNotetrack = -1 * GetMoveDelta( endAnimToPlay, teleportStartTime, teleportEndTime )[ 2 ]; heightAdjustment = abs( self.origin[ 2 ] - endNode.origin[ 2 ] - abs ( GetMoveDelta( endAnimToPlay, teleportEndTime, 1 )[ 2 ] ) ); animScaler = heightAdjustment / animHeightAfterNotetrack; self ScrAgentSetAnimScale( 1, animScaler ); if ( canDoJump ) PlayAnimNUntilNotetrack( animState, 4, "canned_traverse", "stop_teleport" ); else PlayAnimNUntilNotetrack( animState, 3, "canned_traverse", "stop_teleport" ); self ScrAgentSetAnimScale( 1, 1 ); self ScrAgentSetPhysicsMode( "gravity" ); self WaitUntilNotetrack( "canned_traverse", "end" ); } traverseAnimLerp( startAnim, startNode ) { // Make sure we're oriented exactly with the node - // lerp to the correct position for the first part of the anim lerp_time = maps\mp\agents\alien\_alien_anim_utils::getLerpTime( startAnim ); lerp_target_pos = maps\mp\agents\alien\_alien_anim_utils::getPosInSpaceAtAnimTime( startAnim, startNode.origin, startNode.angles, lerp_time ); thread maps\mp\agents\alien\_alien_anim_utils::doLerp( lerp_target_pos, lerp_time ); } traverseClimbDownLerp( startAnim, startNode ) { VERTICAL_DROP = -30; // For climb down, go down when attempt to locate the vertical edge HORIZONTAL_EXTENSION = 60; // Further extend horizontally for nodes places close to the vertical edge doTraverseClimbLerp( startAnim, startNode, VERTICAL_DROP, HORIZONTAL_EXTENSION, true ); } traverseClimbUpLerp( startAnim, startNode ) { VERTICAL_RAISE = 0; // For climb up, go up when attempt to locate the vertical edge HORIZONTAL_EXTENSION = 50; // Further extend horizontally for nodes places placed far from the vertical edge doTraverseClimbLerp( startAnim, startNode, VERTICAL_RAISE, HORIZONTAL_EXTENSION, false ); } doTraverseClimbLerp( startAnim, startNode, verticalProbeDis, horizontalProbeDis, probeForward ) { lerp_time = maps\mp\agents\alien\_alien_anim_utils::getLerpTime( startAnim ); lerp_target_pos = maps\mp\agents\alien\_alien_anim_utils::getPosInSpaceAtAnimTime( startAnim, startNode.origin, startNode.angles, lerp_time ); if ( probeForward ) horizontal_probe_direction = ( lerp_target_pos - startNode.origin ) * ( 1, 1, 0 ); else horizontal_probe_direction = ( startNode.origin - lerp_target_pos ) * ( 1, 1, 0 ); horizontal_offset = vectorNormalize( horizontal_probe_direction ); horizontal_offset *= horizontalProbeDis; anim_end_pos = maps\mp\agents\alien\_alien_anim_utils::getPosInSpaceAtAnimTime( startAnim, startNode.origin, startNode.angles, GetAnimLength( startAnim ) ); end_pos_aligned = alignToVerticalEdge( anim_end_pos, verticalProbeDis, horizontal_offset ); xy_displacement = end_pos_aligned - anim_end_pos; lerp_target_pos += xy_displacement; thread maps\mp\agents\alien\_alien_anim_utils::doLerp( lerp_target_pos, lerp_time ); } alignToVerticalEdge( lerp_target_pos, vertical_displacement, horizontal_offset ) { BACKWARD_SCALAR = 3.0; // When doing a backward trace toward the lerp_target_pos, extend the horizontal_offset further to // make sure we hit the vertical edge lerp_target_pos += horizontal_offset; lerp_target_pos += ( 0, 0, vertical_displacement ); trace_end_pos = lerp_target_pos - horizontal_offset * BACKWARD_SCALAR; trace = bulletTrace( lerp_target_pos, trace_end_pos, false ); lerp_target_pos = trace["position"]; lerp_target_pos += ( 0, 0, -1 * vertical_displacement ); return lerp_target_pos; } canDoJumpForEnd( startnode, endNode, startAnim, jumpAnim ) { TRACE_START_FORWARD_PADDING = 10; TRACE_END_UP_PADDING = ( 0, 0, 10 ); TRACE_CAPSULE_RADIUS = 5; TRACE_CAPSULE_HEIGHT = self.height; startAnimDelta = GetMoveDelta( startAnim, 0, 1 ); startAnimDeltaXY = Length2D ( startAnimDelta ); startAnimDeltaZ = startAnimDelta [ 2 ] * -1; jumpAnimDelta = GetMoveDelta( jumpAnim, 0, 1 ); jumpAnimDeltaXY = Length2D ( jumpAnimDelta ); jumpAnimDeltaZ = jumpAnimDelta[ 2 ] * -1; startToEndXY = VectorNormalize (( endNode.origin - startnode.origin ) * ( 1, 1, 0 )); startAnimEndPos = startnode.origin + startToEndXY * startAnimDeltaXY - ( 0, 0, startAnimDeltaZ ); startAnimEndGroundPos = PhysicsTrace( startAnimEndPos, startAnimEndPos + ( 0, 0, -2000 ) ); startAnimEndAboveGround = ( startAnimEndPos - startAnimEndGroundPos ) [ 2 ]; if ( startAnimEndAboveGround < jumpAnimDeltaZ ) return false; jumpStartPos = startAnimEndGroundPos + ( 0, 0, jumpAnimDeltaZ ); jumpEndPos = startAnimEndGroundPos + startToEndXY * jumpAnimDeltaXY; traceStartPos = jumpStartPos + startToEndXY * TRACE_START_FORWARD_PADDING; traceEndPos = jumpEndPos + TRACE_END_UP_PADDING; return ( self AIPhysicsTracePassed( traceStartPos, traceEndPos, TRACE_CAPSULE_RADIUS, TRACE_CAPSULE_HEIGHT, false ) ); } alienWallRun( startNode, endNode, animState ) { self.oriented = true; startToEnd = endNode.origin - startNode.origin; up = AnglesToUp( endNode.angles ); forward = VectorNormalize( startToEnd ); left = VectorCross( up, forward ); forward = VectorCross( left, up ); right = (0,0,0) - left; startToEndAngles = AxisToAngles( forward, right, up ); self ScrAgentSetOrientMode( "face angle abs", startToEndAngles ); animEntry = self GetAnimEntry( animState, 0 ); time = GetAnimLength( animEntry ); moveDelta = GetMoveDelta( animEntry ); dist = Length( moveDelta ); distToEnd = Length( endNode.origin - self.origin ); lerpTime = time * ( distToEnd / dist ); self ScrAgentDoAnimLerp( self.origin, endNode.origin, lerpTime ); self SetAnimState( animState, 0 ); wait( lerpTime ); self alienWallRun_WaitForAngles( startToEndAngles ); } alienWallRun_AnglesAlmostEqual( angles1, angles2, diff ) { if ( abs( angleClamp180( angles2[0] - angles1[0] ) > diff ) ) return false; if ( abs( angleClamp180( angles2[1] - angles1[1] ) > diff ) ) return false; if ( abs( angleClamp180( angles2[2] - angles1[2] ) > diff ) ) return false; return true; } // do a little extra wait, time out after 0.5s in case something changes. // make sure we're within 5 degrees of our desired angles, but also cancel // out if we're not actually closing in our desired angles because maybe // something changed out our desired angles. // *hocus-pocus-handwavey-insurance* alienWallRun_WaitForAngles( desiredAngles ) { previousDiff = 360; waitTime = 0.5; while ( waitTime > 0 ) { // do an extra check, in addition to the anglesdelta, to see if the angles are close to // each other, because anglesdelta SREs if they're too close. which seems rather unuseful. if ( alienWallRun_AnglesAlmostEqual( self.angles, desiredAngles, 1 ) ) break; diff = AnglesDelta( desiredAngles, self.angles ); if ( diff < 5 || diff >= previousDiff ) break; previousDiff = diff; wait( 0.05 ); waitTime -= 0.05; } }