#using scripts\shared\array_shared; #using scripts\shared\callbacks_shared; #using scripts\shared\math_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\shared\weapons_shared; #using scripts\shared\weapons\_weapons; #using scripts\shared\bots\_bot; #using scripts\shared\bots\bot_buttons; // Dconst wallrun_jumpheight // Dconst wallrun_jumpvelocity // AIPHYS_DEFAULT_HEIGHT // doubleJump_maxUpwardsVelocity = 225 #namespace bot; function Callback_BotEnteredUserEdge( startNode, endNode ) { zDelta = endNode.origin[2] - startNode.origin[2]; xyDist = Distance2D( startNode.origin, endNode.origin ); /* No ladders in BO 3 result = BulletTrace( start, start + ( startDir * TRACE_DISTANCE ), false, self ); if ( result["surfacetype"] == "ladder" ) { self thread climb_traversal(); return; } */ standingViewHeight = GetDvarFloat( "player_standingViewHeight", 0 ); swimWaterHeight = standingViewHeight * GetDvarFloat( "player_swimHeightRatio", 0 ); startWaterHeight = GetWaterHeight( startNode.origin ); startInWater = startWaterHeight != 0 && startWaterHeight > ( startNode.origin[2] + swimWaterHeight ); endWaterHeight = GetWaterHeight( endNode.origin ); endInWater = endWaterHeight != 0 && endWaterHEight > ( endNode.origin[2] + swimWaterHeight ); if ( IsWallrunNode( endNode ) ) { self thread wallrun_traversal( startNode, endNode ); } else if ( startInWater && !endInWater ) { self thread leave_water_traversal( startNode, endNode ); } else if ( startInWater && endInWater ) { self thread swim_traversal( startNode, endNode ); } else if ( zDelta >= 0 ) { self thread jump_up_traversal( startNode, endNode ); } else if ( zDelta < 0 ) { self thread jump_down_traversal( startNode, endNode ); } else { //Can't figure out how to handle traversal self BotReleaseManualControl(); /# PrintLn( "Bot ", self.name, " can't handle traversal!" ); #/ } } /* No ladders in BO3 function climb_traversal( start, end, startDir, endDir ) { self BotSetMoveAngleFromPoint( end ); self thread wait_acrobatics_end(); } */ function traversing() { // IsMantling also checks TRM return !self IsOnGround() || self IsWallRunning() || self IsDoubleJumping() || self IsMantling() || self IsSliding(); } // Water traversals //======================================== function leave_water_traversal( startNode, endNode ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); self thread watch_traversal_end(); // Start the timeout thread self BotSetMoveAngleFromPoint( endNode.origin ); while ( self IsPlayerUnderWater() ) { self bot::press_swim_up(); {wait(.05);}; } while( 1 ) { self bot::press_doublejump_button(); {wait(.05);}; } } function swim_traversal( startNode, endNode ) { self endon( "death" ); level endon( "game_ended" ); self endon( "traversal_end" ); self BotSetMoveAngleFromPoint( endNode.origin ); wait 0.5; self traversal_end(); } // Jump traversals //======================================== function jump_up_traversal( startNode, endNode ) { self endon( "death" ); level endon( "game_ended" ); self endon( "traversal_end" ); self thread watch_traversal_end(); ledgeTop = CheckNavMeshDirection( endNode.origin, self.origin - endNode.origin, 128, 1 ); height = ledgeTop[2] - self.origin[2]; // Do we need to check for an overhang? if ( height <= 72 ) { self thread jump_to( ledgeTop ); return; } /# //Line( ledgeTop, ledgeTop + ( 0, 0, 100 ), ( 0, 1, 0 ), 1, false, 3 * 20 ); //Line( self.origin, self.origin + ( 0, 0, 100 ), ( 0, 1, 0 ), 1, false, 3 * 20 ); #/ dist = Distance2D( self.origin, ledgeTop ); ledgeBottom = CheckNavMeshDirection( self.origin, ledgeTop - self.origin, dist + 15, 1 ); bottomDist = Distance2D( self.origin, ledgeBottom ); // No overhang since the navmesh on the bottom doesn't run under the top if ( bottomDist <= dist ) { self thread jump_to( ledgeTop ); return; } // Use the distance between the ledge and leading edge of the bot dist = dist - 15; height = height - 72; t = height / 80; speed2D = self bot_speed2D(); speed = self GetPlayerSpeed(); moveDist = t * speed2D; if ( !moveDist || dist > moveDist ) { self thread jump_to( ledgeTop ); return; } self BotSetMoveMagnitude( dist / moveDist ); {wait(.05);}; self thread jump_to( ledgeTop ); {wait(.05);}; while( ( self.origin[2] + 72 ) < ledgeTop[2] ) { {wait(.05);}; } self BotSetMoveMagnitude( 1 ); } function jump_down_traversal( startNode, endNode ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); self thread watch_traversal_end(); // Check for a barrier at the beginning fwd = ( endNode.origin[0] - startNode.origin[0], endNode.origin[1] - startNode.origin[1], 0 ); fwd = VectorNormalize( fwd ) * 128; // Don't scrape the trace along the ground start = startNode.origin + ( 0, 0, 16 ); end = startNode.origin + fwd + ( 0, 0, 16 ); result = BulletTrace( start, end, false, self ); if ( result["surfacetype"] != "none" ) { self BotSetMoveAngleFromPoint( endNode.origin ); {wait(.05);}; self bot::tap_jump_button(); return; } // Check if we need to jump to get the distance we need dist = Distance2D( startNode.origin, endNode.origin ); height = startNode.origin[2] - endNode.origin[2]; gravity = self GetPlayerGravity(); t = Sqrt( ( 2 * height ) / gravity ); speed2D = self bot_speed2D(); if ( t * speed2D < dist ) { // We may be able to just fall ledgeTop = CheckNavMeshDirection( startNode.origin, endNode.origin - startNode.origin, 128, 1 ); bottomDist = dist - Distance2D( startNode.origin, ledgeTop ); ledgeBottom = CheckNavMeshDirection( endNode.origin, startNode.origin - endNode.origin, bottomDist, 1 ); meshDist = Distance2D( ledgeTop, ledgeBottom ); if ( meshDist > ( 2 * 15 ) ) { self thread jump_to( endNode.origin ); return; } } self BotSetMoveAngleFromPoint( endNode.origin ); } // Wallrun //======================================== function wallrun_traversal( startNode, endNode, vector ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); self thread watch_traversal_end(); // Navmesh wall normals get tilted wallNormal = GetNavMeshFaceNormal( endNode.origin, 30 ); wallNormal = VectorNormalize( ( wallNormal[0], wallNormal[1], 0 ) ); traversalDir = ( startNode.origin[0] - endNode.origin[0], startNode.origin[1] - endNode.origin[1], 0 ); cross = VectorCross( wallNormal, traversalDir ); runDir = VectorCross( wallNormal, cross ); self BotSetLookAngles( runDir ); self thread jump_to( endNode.origin, vector ); self thread wait_wallrun_begin( startNode, endNode, wallNormal, runDir ); } function wait_wallrun_begin( startNode, endNode, wallNormal, runDir ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); self waittill( "wallrun_begin" ); self thread watch_traversal_end(); // Reset the timeout self BotLookNone(); self BotSetMoveAngle( runDir ); self bot::release_doublejump_button(); index = self GetNodeIndexOnPath( startNode ); index++; exitStartNode = self GetNextTraversalNodeOnPath( index ); if ( isdefined( exitStartNode ) ) { exitEndNode = GetOtherNodeInNegotiationPair( exitStartNode ); if ( isdefined( exitEndNode ) ) { self thread exit_wallrun( exitStartNode, exitEndNode, wallNormal, VectorNormalize( runDir ) ); } } } function exit_wallrun( startNode, endNode, wallNormal, runNormal ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); self thread watch_traversal_end(); // Reset the timeout gravity = self GetPlayerGravity(); // TODO: gadget_speedBurstWallRunJumpVelocity vUp = Sqrt( 40 * 2 * gravity ); tPeak = vUp / gravity; hPeak = self.origin[2] + 40; fallDist = hPeak - endNode.origin[2]; if ( fallDist > 0 ) { tFall = Sqrt( fallDist / ( 0.5 * gravity ) ); } else { // Probably need to do something else here tFall = 0; } t = tPeak + tFall; exitDir = endNode.origin - startNode.origin; dNormal = VectorDot( exitDir, wallNormal ); vNormal = dNormal / t; if ( vNormal <= 200 ) { dot = Sqrt( vNormal / 200 ); vForward = Sqrt( ( 200 * 200 * dot * dot ) - ( vNormal * vNormal ) ); } else { vForward = 0; } // TODO: Handle the case where the solution fails while(1) { {wait(.05);}; endDir = endNode.origin - self.origin; endDist = VectorDot( endDir, runNormal ); vRun = self bot_speed2D(); dForward = ( vRun + vForward ) * t; /*# dorigin = (self.origin[0], self.origin[1], endNode.origin[2] ); Line( dorigin, dorigin + ( wallNormal * dNormal ) ); Line( dorigin, dorigin + ( runNormal * dForward ) ); Line( dorigin, dorigin + ( wallNormal * dNormal ) + ( runNormal * dForward ) ); line( dorigin, endNode.origin ); #*/ if ( endDist <= dForward ) { jumpAngle = ( wallNormal * vNormal ) + ( runNormal * vForward ); if ( IsWallrunNode( endNode ) ) { self thread wallrun_traversal( startNode, endNode, jumpAngle ); } else { self BotSetLookAnglesFromPoint( endNode.origin ); self thread jump_to( endNode.origin, jumpAngle ); } return; } } } function jump_to( target, vector ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); if ( isdefined( vector ) ) { self BotSetMoveAngle( vector ); moveDir = VectorNormalize( ( vector[0], vector[1], 0 ) ); } else { self BotSetMoveAngleFromPoint( target ); targetDelta = target - self.origin; moveDir = VectorNormalize( ( targetDelta[0], targetDelta[1], 0 ) ); } velocity = self GetVelocity( ); velocityDir = VectorNormalize( ( velocity[0], velocity[1], 0 ) ); if ( VectorDot( moveDir, velocityDir ) < 0.94 ) { {wait(.05);}; } self bot::tap_jump_button(); {wait(.05);}; while ( !self IsOnGround() && !self IsMantling() && !self IsWallRunning() && !self bot_hit_target( target ) ) { bot::press_doublejump_button(); if ( !isdefined( vector ) ) { self BotSetMoveAngleFromPoint( target ); } {wait(.05);}; } bot::release_doublejump_button(); } function bot_update_move_angle( target ) { self endon( "death" ); self endon( "traversal_end" ); level endon( "game_ended" ); while ( !self IsMantling() ) { self BotSetMoveAngleFromPoint( target ); {wait(.05);}; } } function bot_hit_target( target ) { velocity = self GetVelocity(); targetDir = target - self.origin; targetDir = ( targetDir[0], targetDir[1], 0 ); // Check for 'overshoot' when going down if ( self.origin[2] > target[2] && VectorDot( velocity, targetDir ) <= 0 ) { return true; } targetDist = Length( targetDir ); targetSpeed = Length( velocity ); if ( targetSpeed == 0 ) { return false; } t = targetDist / targetSpeed; gravity = self GetPlayerGravity(); height = self.origin[2] + velocity[2] * t - ( gravity * t * t * .5 ); /# //projection = ( target[0], target[1], height ); //util::drawcylinder( projection, 15, 2, ( height >= target[2] ? 1 : 1 ) ); #/ return height >= ( target[2] + 32 ); } function bot_speed2D() { velocity = self GetVelocity(); speed2D = Distance2D( velocity, ( 0, 0, 0 ) ); return speed2D; } // Traversal End //======================================== function watch_traversal_end() { self notify( "watch_travesal_end" ); self endon( "death" ); self endon( "traversal_end" ); self endon( "watch_travesal_end" ); level endon( "game_ended" ); self thread wait_traversal_timeout(); self thread watch_start_swimming(); self waittill( "acrobatics_end" ); self thread traversal_end(); } function watch_start_swimming() { self endon( "death" ); self endon( "traversal_end" ); self endon( "watch_travesal_end" ); level endon( "game_ended" ); while( self IsPlayerSwimming() ) { {wait(.05);}; } {wait(.05);}; while( !self IsPlayerSwimming() ) { {wait(.05);}; } self thread traversal_end(); } function wait_traversal_timeout() { self endon( "death" ); self endon( "traversal_end" ); self endon( "watch_travesal_end" ); level endon( "game_ended" ); wait( 8 ); self thread traversal_end(); self BotRequestPath(); } function traversal_end() { self notify( "traversal_end" ); self bot::release_doublejump_button(); self BotLookForward(); self BotSetMoveMagnitude( 1 ); self BotReleaseManualControl(); }