From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- game/server/NextBot/NextBotGroundLocomotion.cpp | 1442 +++++++++++++++++++++++ 1 file changed, 1442 insertions(+) create mode 100644 game/server/NextBot/NextBotGroundLocomotion.cpp (limited to 'game/server/NextBot/NextBotGroundLocomotion.cpp') diff --git a/game/server/NextBot/NextBotGroundLocomotion.cpp b/game/server/NextBot/NextBotGroundLocomotion.cpp new file mode 100644 index 0000000..27f6a52 --- /dev/null +++ b/game/server/NextBot/NextBotGroundLocomotion.cpp @@ -0,0 +1,1442 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// NextBotGroundLocomotion.cpp +// Basic ground-based movement for NextBotCombatCharacters +// Author: Michael Booth, February 2009 +// Note: This is a refactoring of ZombieBotLocomotion from L4D + +#include "cbase.h" + +#include "func_break.h" +#include "func_breakablesurf.h" +#include "activitylist.h" +#include "BasePropDoor.h" + +#include "nav.h" +#include "NextBot.h" +#include "NextBotGroundLocomotion.h" +#include "NextBotUtil.h" +#include "functorutils.h" +#include "SharedFunctorUtils.h" + +#include "tier0/vprof.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely + + +//---------------------------------------------------------------------------------------------------------- +NextBotGroundLocomotion::NextBotGroundLocomotion( INextBot *bot ) : ILocomotion( bot ) +{ + m_nextBot = NULL; + m_ladder = NULL; + m_desiredLean.x = 0.0f; + m_desiredLean.y = 0.0f; + m_desiredLean.z = 0.0f; + + m_bRecomputePostureOnCollision = false; + m_ignorePhysicsPropTimer.Invalidate(); +} + + +//---------------------------------------------------------------------------------------------------------- +NextBotGroundLocomotion::~NextBotGroundLocomotion() +{ +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Reset locomotor to initial state + */ +void NextBotGroundLocomotion::Reset( void ) +{ + BaseClass::Reset(); + m_bRecomputePostureOnCollision = false; + m_ignorePhysicsPropTimer.Invalidate(); + + m_nextBot = static_cast< NextBotCombatCharacter * >( GetBot()->GetEntity() ); + + m_desiredSpeed = 0.0f; + m_velocity = vec3_origin; + m_acceleration = vec3_origin; + + m_desiredLean.x = 0.0f; + m_desiredLean.y = 0.0f; + m_desiredLean.z = 0.0f; + + m_ladder = NULL; + + m_isJumping = false; + m_isJumpingAcrossGap = false; + m_ground = NULL; + m_groundNormal = Vector( 0, 0, 1.0f ); + m_isClimbingUpToLedge = false; + m_isUsingFullFeetTrace = false; + + m_moveVector = Vector( 1, 0, 0 ); + + m_priorPos = m_nextBot->GetPosition(); + m_lastValidPos = m_nextBot->GetPosition(); + + m_inhibitObstacleAvoidanceTimer.Invalidate(); + + m_accumApproachVectors = vec3_origin; + m_accumApproachWeights = 0.0f; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Move the bot along a ladder + */ +bool NextBotGroundLocomotion::TraverseLadder( void ) +{ + // not climbing a ladder right now + return false; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Update internal state + */ +void NextBotGroundLocomotion::Update( void ) +{ + VPROF_BUDGET( "NextBotGroundLocomotion::Update", "NextBot" ); + + BaseClass::Update(); + + const float deltaT = GetUpdateInterval(); + + // apply accumulated position changes + ApplyAccumulatedApproach(); + + // need to do this first thing, because ground constraints, etc, can change it + Vector origPos = GetFeet(); + + IBody *body = GetBot()->GetBodyInterface(); + + if ( TraverseLadder() ) + { + // bot is climbing a ladder + return; + } + + if ( !body->IsPostureMobile() ) + { + // sitting/lying on the ground - no slip + m_acceleration.x = 0.0f; + m_acceleration.y = 0.0f; + m_velocity.x = 0.0f; + m_velocity.y = 0.0f; + } + + bool wasOnGround = IsOnGround(); + + if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) ) + { + // fall if in the air + if ( !IsOnGround() ) + { + // no ground below us - fall + m_acceleration.z -= GetGravity(); + } + + if ( !IsClimbingOrJumping() || m_velocity.z <= 0.0f ) + { + // keep us on the ground + UpdateGroundConstraint(); + } + } + + Vector newPos = GetFeet(); + + // + // Update position physics + // + Vector right( m_moveVector.y, -m_moveVector.x, 0.0f ); + + if ( IsOnGround() ) // || m_isClimbingUpToLedge ) + { + if ( IsAttemptingToMove() ) + { + float forwardSpeed = DotProduct( m_velocity, m_moveVector ); + Vector forwardVelocity = forwardSpeed * m_moveVector; + Vector sideVelocity = DotProduct( m_velocity, right ) * right; + + Vector frictionAccel = vec3_origin; + + // only apply friction along forward direction if we are sliding backwards + if ( forwardSpeed < 0.0f ) + { + frictionAccel = -GetFrictionForward() * forwardVelocity; + } + + // always apply lateral friction to counteract sideslip + frictionAccel += -GetFrictionSideways() * sideVelocity; + + m_acceleration.x += frictionAccel.x; + m_acceleration.y += frictionAccel.y; + } + else + { + // come to a stop if we haven't been told to move + m_acceleration = vec3_origin; + m_velocity = vec3_origin; + } + } + + // compute new position, taking into account MOTION_CONTROLLED animations in progress + if ( body->HasActivityType( IBody::MOTION_CONTROLLED_XY ) ) + { + m_acceleration.x = 0.0f; + m_acceleration.y = 0.0f; + m_velocity.x = GetBot()->GetEntity()->GetAbsVelocity().x; + m_velocity.y = GetBot()->GetEntity()->GetAbsVelocity().y; + } + else + { + // euler integration + m_velocity.x += m_acceleration.x * deltaT; + m_velocity.y += m_acceleration.y * deltaT; + + // euler integration + newPos.x += m_velocity.x * deltaT; + newPos.y += m_velocity.y * deltaT; + } + + if ( body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) ) + { + m_acceleration.z = 0.0f; + m_velocity.z = GetBot()->GetEntity()->GetAbsVelocity().z; + } + else + { + // euler integration + m_velocity.z += m_acceleration.z * deltaT; + + // euler integration + newPos.z += m_velocity.z * deltaT; + } + + // move bot to new position, resolving collisions along the way + UpdatePosition( newPos ); + + + // set actual velocity based on position change after collision resolution step + Vector adjustedVelocity = ( GetFeet() - origPos ) / deltaT; + + if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_XY ) ) + { + m_velocity.x = adjustedVelocity.x; + m_velocity.y = adjustedVelocity.y; + } + + if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) ) + { + m_velocity.z = adjustedVelocity.z; + } + + + // collision resolution may create very high instantaneous velocities, limit it + Vector2D groundVel = m_velocity.AsVector2D(); + m_actualSpeed = groundVel.NormalizeInPlace(); + + if ( IsOnGround() ) + { + if ( m_actualSpeed > GetRunSpeed() ) + { + m_actualSpeed = GetRunSpeed(); + m_velocity.x = m_actualSpeed * groundVel.x; + m_velocity.y = m_actualSpeed * groundVel.y; + } + + // remove downward velocity when landing on the ground + if ( !wasOnGround ) + { + m_velocity.z = 0.0f; + m_acceleration.z = 0.0f; + } + } + else + { + // we're falling. if our velocity has become zero for any reason, shove it forward + const float epsilon = 1.0f; + if ( m_velocity.IsLengthLessThan( epsilon ) ) + { + m_velocity = GetRunSpeed() * GetGroundMotionVector(); + } + } + + // update entity velocity to that of locomotor + m_nextBot->SetAbsVelocity( m_velocity ); + + +#ifdef LEANING + // lean sideways proportional to lateral acceleration + QAngle lean = GetDesiredLean(); + + float sideAccel = DotProduct( right, m_acceleration ); + float slide = sideAccel / GetMaxAcceleration(); + + // max lean depends on how fast we're actually moving + float maxLeanAngle = NextBotLeanMaxAngle.GetFloat() * m_actualSpeed / GetRunSpeed(); + + // actual lean angle is proportional to lateral acceleration (sliding) + float desiredSideLean = -maxLeanAngle * slide; + + lean.y += ( desiredSideLean - lean.y ) * NextBotLeanRate.GetFloat() * deltaT; + + SetDesiredLean( lean ); +#endif // _DEBUG + + + // reset acceleration accumulation + m_acceleration = vec3_origin; + + // debug display + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) ) + { + // track position over time + if ( IsOnGround() ) + { + NDebugOverlay::Cross3D( GetFeet(), 1.0f, 0, 255, 0, true, 15.0f ); + } + else + { + NDebugOverlay::Cross3D( GetFeet(), 1.0f, 0, 255, 255, true, 15.0f ); + } + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Move directly towards given position. + * We need to do this in-air as well to land jumps. + */ +void NextBotGroundLocomotion::Approach( const Vector &rawPos, float goalWeight ) +{ + BaseClass::Approach( rawPos ); + + m_accumApproachVectors += ( rawPos - GetFeet() ) * goalWeight; + m_accumApproachWeights += goalWeight; + m_bRecomputePostureOnCollision = true; +} + + +//---------------------------------------------------------------------------------------------------------- +void NextBotGroundLocomotion::ApplyAccumulatedApproach( void ) +{ + VPROF_BUDGET( "NextBotGroundLocomotion::ApplyAccumulatedApproach", "NextBot" ); + + Vector rawPos = GetFeet(); + + const float deltaT = GetUpdateInterval(); + + if ( deltaT <= 0.0f ) + return; + + if ( m_accumApproachWeights > 0.0f ) + { + Vector approachDelta = m_accumApproachVectors / m_accumApproachWeights; + + // limit total movement to our max speed + float maxMove = GetRunSpeed() * deltaT; + + float desiredMove = approachDelta.NormalizeInPlace(); + if ( desiredMove > maxMove ) + { + desiredMove = maxMove; + } + + rawPos += desiredMove * approachDelta; + + m_accumApproachVectors = vec3_origin; + m_accumApproachWeights = 0.0f; + } + + // can only move in 2D - geometry moves us up and down + Vector pos( rawPos.x, rawPos.y, GetFeet().z ); + + if ( !GetBot()->GetBodyInterface()->IsPostureMobile() ) + { + // body is not in a movable state right now + return; + } + + Vector currentPos = m_nextBot->GetPosition(); + + // compute unit vector to goal position + m_moveVector = pos - currentPos; + m_moveVector.z = 0.0f; + float change = m_moveVector.NormalizeInPlace(); + + const float epsilon = 0.001f; + if ( change < epsilon ) + { + // no motion + m_forwardLean = 0.0f; + m_sideLean = 0.0f; + return; + } + +/* + // lean forward/backward based on acceleration + float desiredLean = m_acceleration / NextBotLeanForwardAccel.GetFloat(); + + QAngle lean = GetDesiredLean(); + + lean.x = NextBotLeanMaxAngle.GetFloat() * clamp( desiredLean, -1.0f, 1.0f ); + + SetDesiredLean( lean ); +*/ + + Vector newPos; + + // if we just started a jump, don't snap to the ground - let us get in the air first + if ( DidJustJump() || !IsOnGround() ) + { + if ( false && m_isClimbingUpToLedge ) // causes bots to hang in air stuck against edges + { + // drive towards the approach position in XY to help reach ledge + m_moveVector = m_ledgeJumpGoalPos - currentPos; + m_moveVector.z = 0.0f; + m_moveVector.NormalizeInPlace(); + + m_acceleration += GetMaxAcceleration() * m_moveVector; + } + } + else if ( IsOnGround() ) + { + // on the ground - move towards the approach position + m_isClimbingUpToLedge = false; + + // snap forward movement vector along floor + const Vector &groundNormal = GetGroundNormal(); + + Vector left( -m_moveVector.y, m_moveVector.x, 0.0f ); + m_moveVector = CrossProduct( left, groundNormal ); + m_moveVector.NormalizeInPlace(); + + // limit maximum forward speed from self-acceleration + float forwardSpeed = DotProduct( m_velocity, m_moveVector ); + + float maxSpeed = MIN( m_desiredSpeed, GetSpeedLimit() ); + + if ( forwardSpeed < maxSpeed ) + { + float ratio = ( forwardSpeed <= 0.0f ) ? 0.0f : ( forwardSpeed / maxSpeed ); + float governor = 1.0f - ( ratio * ratio * ratio * ratio ); + + // accelerate towards goal + m_acceleration += governor * GetMaxAcceleration() * m_moveVector; + } + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Move the bot to the precise given position immediately, + */ +void NextBotGroundLocomotion::DriveTo( const Vector &pos ) +{ + BaseClass::DriveTo( pos ); + m_bRecomputePostureOnCollision = true; + UpdatePosition( pos ); +} + + +//-------------------------------------------------------------------------------------------- +/* + * Trace filter solely for use with DetectCollision() below. + */ +class GroundLocomotionCollisionTraceFilter : public CTraceFilterSimple +{ +public: + GroundLocomotionCollisionTraceFilter( INextBot *me, const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup ) + { + m_me = me; + } + + virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) + { + if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) ) + { + CBaseEntity *entity = EntityFromEntityHandle( pServerEntity ); + + // don't collide with ourself + if ( entity && m_me->IsSelf( entity ) ) + return false; + + return m_me->GetLocomotionInterface()->ShouldCollideWith( entity ); + } + + return false; + } + + INextBot *m_me; +}; + + +//---------------------------------------------------------------------------------------------------------- +/** + * Check for collisions during move and attempt to resolve them + */ +bool NextBotGroundLocomotion::DetectCollision( trace_t *pTrace, int &recursionLimit, const Vector &from, const Vector &to, const Vector &vecMins, const Vector &vecMaxs ) +{ + IBody *body = GetBot()->GetBodyInterface(); + + CBaseEntity *ignore = m_ignorePhysicsPropTimer.IsElapsed() ? NULL : m_ignorePhysicsProp; + GroundLocomotionCollisionTraceFilter filter( GetBot(), ignore, body->GetCollisionGroup() ); + + TraceHull( from, to, vecMins, vecMaxs, body->GetSolidMask(), &filter, pTrace ); + + if ( !pTrace->DidHit() ) + return false; + + // + // A collision occurred - resolve it + // + + // bust through "flimsy" breakables and keep on going + if ( pTrace->DidHitNonWorldEntity() && pTrace->m_pEnt != NULL ) + { + CBaseEntity *other = pTrace->m_pEnt; + + if ( !other->MyCombatCharacterPointer() && IsEntityTraversable( other, IMMEDIATELY ) /*&& IsFlimsy( other )*/ ) + { + if ( recursionLimit <= 0 ) + return true; + + --recursionLimit; + + // break the weak breakable we collided with + CTakeDamageInfo damageInfo( GetBot()->GetEntity(), GetBot()->GetEntity(), 100.0f, DMG_CRUSH ); + CalculateExplosiveDamageForce( &damageInfo, GetMotionVector(), pTrace->endpos ); + other->TakeDamage( damageInfo ); + + // retry trace now that the breakable is out of the way + return DetectCollision( pTrace, recursionLimit, from, to, vecMins, vecMaxs ); + } + } + + /// @todo Only invoke OnContact() and Touch() once per collision pair + // inform other components of collision + if ( GetBot()->ShouldTouch( pTrace->m_pEnt ) ) + { + GetBot()->OnContact( pTrace->m_pEnt, pTrace ); + } + + INextBot *them = dynamic_cast< INextBot * >( pTrace->m_pEnt ); + if ( them && them->ShouldTouch( m_nextBot ) ) + { + /// @todo construct mirror of trace + them->OnContact( m_nextBot ); + } + else + { + pTrace->m_pEnt->Touch( GetBot()->GetEntity() ); + } + + return true; +} + + +//---------------------------------------------------------------------------------------------------------- +Vector NextBotGroundLocomotion::ResolveCollision( const Vector &from, const Vector &to, int recursionLimit ) +{ + VPROF_BUDGET( "NextBotGroundLocomotion::ResolveCollision", "NextBotExpensive" ); + + IBody *body = GetBot()->GetBodyInterface(); + if ( body == NULL || recursionLimit < 0 ) + { + Assert( !m_bRecomputePostureOnCollision ); + return to; + } + + // Only bother to recompute posture if we're currently standing or crouching + if ( m_bRecomputePostureOnCollision ) + { + if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) ) + { + m_bRecomputePostureOnCollision = false; + } + } + + // get bounding limits, ignoring step-upable height + bool bPerformCrouchTest = false; + Vector mins; + Vector maxs; + if ( m_isUsingFullFeetTrace ) + { + mins = body->GetHullMins(); + } + else + { + mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() ); + } + if ( !m_bRecomputePostureOnCollision ) + { + maxs = body->GetHullMaxs(); + if ( mins.z >= maxs.z ) + { + // if mins.z is greater than maxs.z, the engine will Assert + // in UTIL_TraceHull, and it won't work as advertised. + mins.z = maxs.z - 2.0f; + } + } + else + { + const float halfSize = body->GetHullWidth() / 2.0f; + maxs.Init( halfSize, halfSize, body->GetStandHullHeight() ); + bPerformCrouchTest = true; + } + + trace_t trace; + Vector desiredGoal = to; + Vector resolvedGoal; + IBody::PostureType nPosture = IBody::STAND; + while( true ) + { + bool bCollided = DetectCollision( &trace, recursionLimit, from, desiredGoal, mins, maxs ); + if ( !bCollided ) + { + resolvedGoal = desiredGoal; + break; + } + + // If we hit really close to our target, then stop + if ( !trace.startsolid && desiredGoal.DistToSqr( trace.endpos ) < 1.0f ) + { + resolvedGoal = trace.endpos; + break; + } + + // Check for crouch test, if it's necessary + // Don't bother about checking for crouch if we hit an actor + // Also don't bother checking for crouch if we hit a plane that pushes us upwards + if ( bPerformCrouchTest ) + { + // Don't do this work twice + bPerformCrouchTest = false; + + nPosture = body->GetDesiredPosture(); + + if ( !trace.m_pEnt->MyNextBotPointer() && !trace.m_pEnt->IsPlayer() ) + { + // Here, our standing trace hit the world or something non-breakable + // If we're not currently crouching, then see if we could travel + // the entire distance if we were crouched + if ( nPosture != IBody::CROUCH ) + { + trace_t crouchTrace; + NextBotTraversableTraceFilter crouchFilter( GetBot(), ILocomotion::IMMEDIATELY ); + Vector vecCrouchMax( maxs.x, maxs.y, body->GetCrouchHullHeight() ); + TraceHull( from, desiredGoal, mins, vecCrouchMax, body->GetSolidMask(), &crouchFilter, &crouchTrace ); + if ( crouchTrace.fraction >= 1.0f && !crouchTrace.startsolid ) + { + nPosture = IBody::CROUCH; + } + } + } + else if ( nPosture == IBody::CROUCH ) + { + // Here, our standing trace hit an actor + + // NOTE: This test occurs almost never, based on my tests + // Converts from crouch to stand in the case where the player + // is currently crouching, *and* his first trace (with the standing hull) + // hits an actor *and* if he didn't hit that actor, he could have + // moved standing the entire way to his desired endpoint + trace_t standTrace; + NextBotTraversableTraceFilter standFilter( GetBot(), ILocomotion::IMMEDIATELY ); + TraceHull( from, desiredGoal, mins, maxs, body->GetSolidMask(), &standFilter, &standTrace ); + if ( standTrace.fraction >= 1.0f && !standTrace.startsolid ) + { + nPosture = IBody::STAND; + } + } + + // Our first trace was based on the standing hull. + // If we need be crouched, the trace was bogus; we need to do another + if ( nPosture == IBody::CROUCH ) + { + maxs.z = body->GetCrouchHullHeight(); + continue; + } + } + + if ( trace.startsolid ) + { + // stuck inside solid; don't move + + if ( trace.m_pEnt && !trace.m_pEnt->IsWorld() ) + { + // only ignore physics props that are not doors + if ( dynamic_cast< CPhysicsProp * >( trace.m_pEnt ) != NULL && dynamic_cast< CBasePropDoor * >( trace.m_pEnt ) == NULL ) + { + IPhysicsObject *physics = trace.m_pEnt->VPhysicsGetObject(); + if ( physics && physics->IsMoveable() ) + { + // we've intersected a (likely moving) physics prop - ignore it for awhile so we can move out of it + m_ignorePhysicsProp = trace.m_pEnt; + m_ignorePhysicsPropTimer.Start( 1.0f ); + } + } + } + + // return to last known non-interpenetrating position + resolvedGoal = m_lastValidPos; + + break; + } + + if ( --recursionLimit <= 0 ) + { + // reached recursion limit, no more adjusting allowed + resolvedGoal = trace.endpos; + break; + } + + // never slide downwards/concave to avoid getting stuck in the ground + if ( trace.plane.normal.z < 0.0f ) + { + trace.plane.normal.z = 0.0f; + trace.plane.normal.NormalizeInPlace(); + } + + // slide off of surface we hit + Vector fullMove = desiredGoal - from; + Vector leftToMove = fullMove * ( 1.0f - trace.fraction ); + + // obey climbing slope limit + if ( !body->HasActivityType( IBody::MOTION_CONTROLLED_Z ) && + trace.plane.normal.z < GetTraversableSlopeLimit() && + fullMove.z > 0.0f ) + { + fullMove.z = 0.0f; + trace.plane.normal.z = 0.0f; + trace.plane.normal.NormalizeInPlace(); + } + + float blocked = DotProduct( trace.plane.normal, leftToMove ); + + Vector unconstrained = fullMove - blocked * trace.plane.normal; + + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) ) + { + NDebugOverlay::Line( trace.endpos, + trace.endpos + 20.0f * trace.plane.normal, + 255, 0, 150, true, 15.0f ); + } + + // check for collisions along remainder of move + // But don't bother if we're not going to deflect much + Vector remainingMove = from + unconstrained; + if ( remainingMove.DistToSqr( trace.endpos ) < 1.0f ) + { + resolvedGoal = trace.endpos; + break; + } + + desiredGoal = remainingMove; + } + + if ( !trace.startsolid ) + { + m_lastValidPos = resolvedGoal; + } + + if ( m_bRecomputePostureOnCollision ) + { + m_bRecomputePostureOnCollision = false; + + if ( !body->IsActualPosture( nPosture ) ) + { + body->SetDesiredPosture( nPosture ); + } + } + + return resolvedGoal; +} + + +//-------------------------------------------------------------------------------------------------------- +/** + * Collect the closest actors + */ +class ClosestActorsScan +{ +public: + ClosestActorsScan( const Vector &spot, int team, float maxRange = 0.0f, CBaseCombatCharacter *ignore = NULL ) + { + m_spot = spot; + m_team = team; + m_close = NULL; + + if ( maxRange > 0.0f ) + { + m_closeRangeSq = maxRange * maxRange; + } + else + { + m_closeRangeSq = 999999999.9f; + } + + m_ignore = ignore; + } + + bool operator() ( CBaseCombatCharacter *actor ) + { + if (actor == m_ignore) + return true; + + if (actor->IsAlive() && (m_team == TEAM_ANY || actor->GetTeamNumber() == m_team)) + { + Vector to = actor->WorldSpaceCenter() - m_spot; + float rangeSq = to.LengthSqr(); + if (rangeSq < m_closeRangeSq) + { + m_closeRangeSq = rangeSq; + m_close = actor; + } + } + return true; + } + + CBaseCombatCharacter *GetActor( void ) const + { + return m_close; + } + + bool IsCloserThan( float range ) + { + return (m_closeRangeSq < (range * range)); + } + + bool IsFartherThan( float range ) + { + return (m_closeRangeSq > (range * range)); + } + + Vector m_spot; + int m_team; + CBaseCombatCharacter *m_close; + float m_closeRangeSq; + CBaseCombatCharacter *m_ignore; +}; + + +#ifdef SKIPME +//---------------------------------------------------------------------------------------------------------- +/** + * Push away zombies that are interpenetrating + */ +Vector NextBotGroundLocomotion::ResolveZombieCollisions( const Vector &pos ) +{ + Vector adjustedNewPos = pos; + + Infected *me = m_nextBot->MyInfectedPointer(); + const float hullWidth = me->GetBodyInterface()->GetHullWidth(); + + // only avoid if we're actually trying to move somewhere, and are enraged + if ( me != NULL && !IsUsingLadder() && !IsClimbingOrJumping() && IsOnGround() && m_nextBot->IsAlive() && IsAttemptingToMove() /*&& GetBot()->GetBodyInterface()->IsArousal( IBody::INTENSE )*/ ) + { + VPROF_BUDGET( "NextBotGroundLocomotion::ResolveZombieCollisions", "NextBot" ); + + const CUtlVector< CHandle< Infected > > &neighbors = me->GetNeighbors(); + Vector avoid = vec3_origin; + float avoidWeight = 0.0f; + + FOR_EACH_VEC( neighbors, it ) + { + Infected *them = neighbors[ it ]; + + if ( them ) + { + Vector toThem = them->GetAbsOrigin() - me->GetAbsOrigin(); + toThem.z = 0.0f; + + float range = toThem.NormalizeInPlace(); + + if ( range < hullWidth ) + { + // these two infected are in contact + me->Touch( them ); + + // move out of contact + float penetration = hullWidth - range; + + float weight = 1.0f + ( 2.0f * penetration/hullWidth ); + avoid += -weight * toThem; + avoidWeight += weight; + } + } + } + + if ( avoidWeight > 0.0f ) + { + adjustedNewPos += 3.0f * ( avoid / avoidWeight ); + } + } + + return adjustedNewPos; +} +#endif // _DEBUG + + +//---------------------------------------------------------------------------------------------------------- +/** + * Move to newPos, resolving any collisions along the way + */ +void NextBotGroundLocomotion::UpdatePosition( const Vector &newPos ) +{ + VPROF_BUDGET( "NextBotGroundLocomotion::UpdatePosition", "NextBot" ); + + if ( NextBotStop.GetBool() || (m_nextBot->GetFlags() & FL_FROZEN) != 0 || newPos == m_nextBot->GetPosition() ) + { + return; + } + + // avoid very nearby Actors to simulate "mushy" collisions between actors in contact with each other + //Vector adjustedNewPos = ResolveZombieCollisions( newPos ); + Vector adjustedNewPos = newPos; + + // check for collisions during move and resolve them + const int recursionLimit = 3; + Vector safePos = ResolveCollision( m_nextBot->GetPosition(), adjustedNewPos, recursionLimit ); + + // set the bot's position + if ( GetBot()->GetIntentionInterface()->IsPositionAllowed( GetBot(), safePos ) != ANSWER_NO ) + { + m_nextBot->SetPosition( safePos ); + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Prevent bot from sliding through floor, and snap to the ground if we're very near it + */ +void NextBotGroundLocomotion::UpdateGroundConstraint( void ) +{ + VPROF_BUDGET( "NextBotGroundLocomotion::UpdateGroundConstraint", "NextBotExpensive" ); + + // if we're up on the upward arc of our jump, don't interfere by snapping to ground + // don't do ground constraint if we're climbing a ladder + if ( DidJustJump() || IsAscendingOrDescendingLadder() ) + { + m_isUsingFullFeetTrace = false; + return; + } + + IBody *body = GetBot()->GetBodyInterface(); + if ( body == NULL ) + { + return; + } + + float halfWidth = body->GetHullWidth()/2.0f; + + // since we only care about ground collisions, keep hull short to avoid issues with low ceilings + /// @TODO: We need to also check actual hull height to avoid interpenetrating the world + float hullHeight = GetStepHeight(); + + // always need tolerance even when jumping/falling to make sure we detect ground penetration + // must be at least step height to avoid 'falling' down stairs + const float stickToGroundTolerance = GetStepHeight() + 0.01f; + + trace_t ground; + NextBotTraceFilterIgnoreActors filter( m_nextBot, body->GetCollisionGroup() ); + + TraceHull( m_nextBot->GetPosition() + Vector( 0, 0, GetStepHeight() + 0.001f ), + m_nextBot->GetPosition() + Vector( 0, 0, -stickToGroundTolerance ), + Vector( -halfWidth, -halfWidth, 0 ), + Vector( halfWidth, halfWidth, hullHeight ), + body->GetSolidMask(), &filter, &ground ); + + if ( ground.startsolid ) + { + // we're inside the ground - bad news + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) && !( gpGlobals->framecount % 60 ) ) + { + DevMsg( "%3.2f: Inside ground, ( %.0f, %.0f, %.0f )\n", gpGlobals->curtime, m_nextBot->GetPosition().x, m_nextBot->GetPosition().y, m_nextBot->GetPosition().z ); + } + return; + } + + if ( ground.fraction < 1.0f ) + { + // there is ground below us + m_groundNormal = ground.plane.normal; + + m_isUsingFullFeetTrace = false; + + // zero velocity normal to the ground + float normalVel = DotProduct( m_groundNormal, m_velocity ); + m_velocity -= normalVel * m_groundNormal; + + // check slope limit + if ( ground.plane.normal.z < GetTraversableSlopeLimit() ) + { + // too steep to stand here + + // too steep to be ground - treat it like a wall hit + if ( ( m_velocity.x * ground.plane.normal.x + m_velocity.y * ground.plane.normal.y ) <= 0.0f ) + { + GetBot()->OnContact( ground.m_pEnt, &ground ); + } + + // we're contacting some kind of ground + // zero accelerations normal to the ground + + float normalAccel = DotProduct( m_groundNormal, m_acceleration ); + m_acceleration -= normalAccel * m_groundNormal; + + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) ) + { + DevMsg( "%3.2f: NextBotGroundLocomotion - Too steep to stand here\n", gpGlobals->curtime ); + NDebugOverlay::Line( GetFeet(), GetFeet() + 20.0f * ground.plane.normal, 255, 150, 0, true, 5.0f ); + } + + // clear out upward velocity so we don't walk up lightpoles + m_velocity.z = MIN( 0, m_velocity.z ); + m_acceleration.z = MIN( 0, m_acceleration.z ); + + return; + } + + // inform other components of collision if we didn't land on the 'world' + if ( ground.m_pEnt && !ground.m_pEnt->IsWorld() ) + { + GetBot()->OnContact( ground.m_pEnt, &ground ); + } + + // snap us to the ground + m_nextBot->SetPosition( ground.endpos ); + + if ( !IsOnGround() ) + { + // just landed + m_nextBot->SetGroundEntity( ground.m_pEnt ); + m_ground = ground.m_pEnt; + + // landing stops any jump in progress + m_isJumping = false; + m_isJumpingAcrossGap = false; + + GetBot()->OnLandOnGround( ground.m_pEnt ); + } + } + else + { + // not on the ground + if ( IsOnGround() ) + { + GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() ); + if ( !IsClimbingUpToLedge() && !IsJumpingAcrossGap() ) + { + m_isUsingFullFeetTrace = true; // We're in the air and there's space below us, so use the full trace + m_acceleration.z -= GetGravity(); // start our gravity now + } + } + } +} + + +//---------------------------------------------------------------------------------------------------------- +/* +void NextBotGroundLocomotion::StandUp( void ) +{ + // make sure there is room to stand + trace_t result; + const float halfSize = GetHullWidth()/3.0f; + Vector standHullMin( -halfSize, -halfSize, GetStepHeight() + 0.1f ); + Vector standHullMax( halfSize, halfSize, GetStandHullHeight() ); + + TraceHull( GetFeet(), GetFeet(), standHullMin, standHullMax, MASK_NPCSOLID, m_nextBot, MASK_DEFAULTPLAYERSOLID, &result ); + + if ( result.fraction >= 1.0f && !result.startsolid ) + { + m_isCrouching = false; + } +} +*/ + + +//---------------------------------------------------------------------------------------------------------- +/** + * Initiate a climb to an adjacent high ledge + */ +bool NextBotGroundLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ) +{ + return false; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Initiate a jump across an empty volume of space to far side + */ +void NextBotGroundLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ) +{ + // can only jump if we're on the ground + if ( !IsOnGround() ) + { + return; + } + + IBody *body = GetBot()->GetBodyInterface(); + if ( !body->StartActivity( ACT_JUMP ) ) + { + // body can't jump right now + return; + } + + + // scale impulse to land on target + Vector toGoal = landingGoal - GetFeet(); + + // equation doesn't work if we're jumping upwards + float height = toGoal.z; + toGoal.z = 0.0f; + + float range = toGoal.NormalizeInPlace(); + + // jump out at 45 degree angle + const float cos45 = 0.7071f; + + // avoid division by zero + if ( height > 0.9f * range ) + { + height = 0.9f * range; + } + + // ballistic equation to find initial velocity assuming 45 degree inclination and landing at give range and height + float launchVel = ( range / cos45 ) / sqrt( ( 2.0f * ( range - height ) ) / GetGravity() ); + + Vector up( 0, 0, 1 ); + Vector ahead = up + toGoal; + ahead.NormalizeInPlace(); + + //m_velocity = cos45 * launchVel * ahead; + m_velocity = launchVel * ahead; + m_acceleration = vec3_origin; + + m_isJumping = true; + m_isJumpingAcrossGap = true; + m_isClimbingUpToLedge = false; + + GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() ); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Initiate a simple undirected jump in the air + */ +void NextBotGroundLocomotion::Jump( void ) +{ + // can only jump if we're on the ground + if ( !IsOnGround() ) + { + return; + } + + IBody *body = GetBot()->GetBodyInterface(); + if ( !body->StartActivity( ACT_JUMP ) ) + { + // body can't jump right now + return; + } + + // jump straight up + m_velocity.z = sqrt( 2.0f * GetGravity() * GetMaxJumpHeight() ); + + m_isJumping = true; + m_isClimbingUpToLedge = false; + + GetBot()->OnLeaveGround( m_nextBot->GetGroundEntity() ); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Set movement speed to running + */ +void NextBotGroundLocomotion::Run( void ) +{ + m_desiredSpeed = GetRunSpeed(); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Set movement speed to walking + */ +void NextBotGroundLocomotion::Walk( void ) +{ + m_desiredSpeed = GetWalkSpeed(); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Set movement speed to stopeed + */ +void NextBotGroundLocomotion::Stop( void ) +{ + m_desiredSpeed = 0.0f; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Return true if standing on something + */ +bool NextBotGroundLocomotion::IsOnGround( void ) const +{ + return (m_nextBot->GetGroundEntity() != NULL); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Invoked when bot leaves ground for any reason + */ +void NextBotGroundLocomotion::OnLeaveGround( CBaseEntity *ground ) +{ + m_nextBot->SetGroundEntity( NULL ); + m_ground = NULL; + + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) ) + { + DevMsg( "%3.2f: NextBotGroundLocomotion::OnLeaveGround\n", gpGlobals->curtime ); + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Invoked when bot lands on the ground after being in the air + */ +void NextBotGroundLocomotion::OnLandOnGround( CBaseEntity *ground ) +{ + if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) ) + { + DevMsg( "%3.2f: NextBotGroundLocomotion::GetBot()->OnLandOnGround\n", gpGlobals->curtime ); + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Get maximum speed bot can reach, regardless of desired speed + */ +float NextBotGroundLocomotion::GetSpeedLimit( void ) const +{ + // if we're crouched, move at reduced speed + if ( !GetBot()->GetBodyInterface()->IsActualPosture( IBody::STAND ) ) + { + return 0.75f * GetRunSpeed(); + } + + // no limit + return 99999999.9f; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Climb the given ladder to the top and dismount + */ +void NextBotGroundLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) +{ + // if we're already climbing this ladder, don't restart + if ( m_ladder == ladder && m_isGoingUpLadder ) + { + return; + } + + m_ladder = ladder; + m_ladderDismountGoal = dismountGoal; + m_isGoingUpLadder = true; + + IBody *body = GetBot()->GetBodyInterface(); + if ( body ) + { + // line them up to climb in XY + Vector mountSpot = m_ladder->m_bottom + m_ladder->GetNormal() * (0.75f * body->GetHullWidth()); + mountSpot.z = GetBot()->GetPosition().z; + + UpdatePosition( mountSpot ); + + body->StartActivity( ACT_CLIMB_UP, IBody::MOTION_CONTROLLED_Z ); + } +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Descend the given ladder to the bottom and dismount + */ +void NextBotGroundLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) +{ + // if we're already descending this ladder, don't restart + if ( m_ladder == ladder && !m_isGoingUpLadder ) + { + return; + } + + m_ladder = ladder; + m_ladderDismountGoal = dismountGoal; + m_isGoingUpLadder = false; + + IBody *body = GetBot()->GetBodyInterface(); + if ( body ) + { + // line them up to climb in XY + Vector mountSpot = m_ladder->m_top + m_ladder->GetNormal() * (0.75f * body->GetHullWidth()); + mountSpot.z = GetBot()->GetPosition().z; + + UpdatePosition( mountSpot ); + + float ladderYaw = UTIL_VecToYaw( -m_ladder->GetNormal() ); + + QAngle angles = m_nextBot->GetLocalAngles(); + angles.y = ladderYaw; + + m_nextBot->SetLocalAngles( angles ); + + body->StartActivity( ACT_CLIMB_DOWN, IBody::MOTION_CONTROLLED_Z ); + } +} + + +//---------------------------------------------------------------------------------------------------------- +bool NextBotGroundLocomotion::IsUsingLadder( void ) const +{ + return ( m_ladder != NULL ); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * We are actually on the ladder right now, either climbing up or down + */ +bool NextBotGroundLocomotion::IsAscendingOrDescendingLadder( void ) const +{ + return IsUsingLadder(); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Return position of "feet" - point below centroid of bot at feet level + */ +const Vector &NextBotGroundLocomotion::GetFeet( void ) const +{ + return m_nextBot->GetPosition(); +} + + +//---------------------------------------------------------------------------------------------------------- +const Vector & NextBotGroundLocomotion::GetAcceleration( void ) const +{ + return m_acceleration; +} + + +//---------------------------------------------------------------------------------------------------------- +void NextBotGroundLocomotion::SetAcceleration( const Vector &accel ) +{ + m_acceleration = accel; +} + + +//---------------------------------------------------------------------------------------------------------- +void NextBotGroundLocomotion::SetVelocity( const Vector &vel ) +{ + m_velocity = vel; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Return current world space velocity + */ +const Vector &NextBotGroundLocomotion::GetVelocity( void ) const +{ + return m_velocity; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Invoked when an bot reaches its MoveTo goal + */ +void NextBotGroundLocomotion::OnMoveToSuccess( const Path *path ) +{ + // stop + m_velocity = vec3_origin; + m_acceleration = vec3_origin; +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Invoked when an bot fails to reach a MoveTo goal + */ +void NextBotGroundLocomotion::OnMoveToFailure( const Path *path, MoveToFailureType reason ) +{ + // stop + m_velocity = vec3_origin; + m_acceleration = vec3_origin; +} + + +//---------------------------------------------------------------------------------------------------------- +bool NextBotGroundLocomotion::DidJustJump( void ) const +{ + return IsClimbingOrJumping() && (m_nextBot->GetAbsVelocity().z > 0.0f); +} + + +//---------------------------------------------------------------------------------------------------------- +/** + * Rotate body to face towards "target" + */ +void NextBotGroundLocomotion::FaceTowards( const Vector &target ) +{ + const float deltaT = GetUpdateInterval(); + + QAngle angles = m_nextBot->GetLocalAngles(); + + float desiredYaw = UTIL_VecToYaw( target - GetFeet() ); + + float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y ); + + float deltaYaw = GetMaxYawRate() * deltaT; + + if (angleDiff < -deltaYaw) + { + angles.y -= deltaYaw; + } + else if (angleDiff > deltaYaw) + { + angles.y += deltaYaw; + } + else + { + angles.y += angleDiff; + } + + m_nextBot->SetLocalAngles( angles ); +} + + + -- cgit v1.2.3