587 lines
18 KiB
Plaintext
587 lines
18 KiB
Plaintext
#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();
|
|
}
|