boiii-scripts/shared/bots/bot_traversals.gsc
2023-04-13 17:30:38 +02:00

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