void CNPC_Stalker::AddZigZagToPath(void) { // If already on a detour don't add a zigzag if (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR) { return; } // If enemy isn't facing me or occluded, don't add a zigzag if (HasCondition(COND_ENEMY_OCCLUDED) || !HasCondition ( COND_ENEMY_FACING_ME )) { return; } Vector waypointPos = GetNavigator()->GetCurWaypointPos(); Vector waypointDir = (waypointPos - GetAbsOrigin()); // If the distance to the next node is greater than ZIG_ZAG_SIZE // then add a random zig/zag to the path if (waypointDir.LengthSqr() > ZIG_ZAG_SIZE) { // Pick a random distance for the zigzag (less that sqrt(ZIG_ZAG_SIZE) float distance = random->RandomFloat( 30, 60 ); // Get me a vector orthogonal to the direction of motion VectorNormalize( waypointDir ); Vector vDirUp(0,0,1); Vector vDir; CrossProduct( waypointDir, vDirUp, vDir); // Pick a random direction (left/right) for the zigzag if (random->RandomInt(0,1)) { vDir = -1 * vDir; } // Get zigzag position in direction of target waypoint Vector zigZagPos = GetAbsOrigin() + waypointDir * 60; // Now offset zigZagPos = zigZagPos + (vDir * distance); // Now make sure that we can still get to the zigzag position and the waypoint AIMoveTrace_t moveTrace1, moveTrace2; GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), zigZagPos, GetAITraceMask(), NULL, &moveTrace1); GetMoveProbe()->MoveLimit( NAV_GROUND, zigZagPos, waypointPos, GetAITraceMask(), NULL, &moveTrace2); if ( !IsMoveBlocked( moveTrace1 ) && !IsMoveBlocked( moveTrace2 ) ) { GetNavigator()->PrependWaypoint( zigZagPos, NAV_GROUND, bits_WP_TO_DETOUR ); } } }
//----------------------------------------------------------------------------- // Step iteratively toward a destination position //----------------------------------------------------------------------------- AIMotorMoveResult_t CAI_Motor::MoveGroundStep( const Vector &newPos, CBaseEntity *pMoveTarget, float yaw, bool bAsFarAsCan, AIMoveTrace_t *pTraceResult ) { // By definition, this will produce different results than GroundMoveLimit() // because there's no guarantee that it will step exactly one step // See how far toward the new position we can step... // But don't actually test for ground geometric validity; // if it isn't valid, there's not much we can do about it AIMoveTrace_t moveTrace; GetMoveProbe()->TestGroundMove( GetLocalOrigin(), newPos, MASK_NPCSOLID, AITGM_IGNORE_FLOOR, &moveTrace ); if ( pTraceResult ) { *pTraceResult = moveTrace; } bool bHitTarget = (moveTrace.pObstruction && (pMoveTarget == moveTrace.pObstruction )); // Move forward either if there was no obstruction or if we're told to // move as far as we can, regardless bool bIsBlocked = IsMoveBlocked(moveTrace.fStatus); if ( !bIsBlocked || bAsFarAsCan || bHitTarget ) { // The true argument here causes it to touch all triggers // in the volume swept from the previous position to the current position UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true); // skip tiny steps, but notify the shadow object of any large steps if ( moveTrace.flStepUpDistance > 0.1f ) { float height = clamp( moveTrace.flStepUpDistance, 0, StepHeight() ); IPhysicsObject *pPhysicsObject = GetOuter()->VPhysicsGetObject(); if ( pPhysicsObject ) { IPhysicsShadowController *pShadow = pPhysicsObject->GetShadowController(); if ( pShadow ) { pShadow->StepUp( height ); } } } if ( yaw != -1 ) { QAngle angles = GetLocalAngles(); angles.y = yaw; SetLocalAngles( angles ); } if ( bHitTarget ) return AIM_PARTIAL_HIT_TARGET; if ( !bIsBlocked ) return AIM_SUCCESS; if ( moveTrace.fStatus == AIMR_BLOCKED_NPC ) return AIM_PARTIAL_HIT_NPC; return AIM_PARTIAL_HIT_WORLD; } return AIM_FAILED; }
// make this alien jump off the head of the ent he's standing on bool CASW_Alien_Jumper::DoJumpOffHead() { //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; if (!(GetGroundEntity()) || GetNavType() == NAV_JUMP ) return false; // force this drone to have jumping capabilities m_bDisableJump = false; CapabilitiesAdd( bits_CAP_MOVE_JUMP ); //Vector vecDest = RandomVector(-1, 1); //vecDest.z = 0; //vecDest *= random->RandomFloat(30, 100); Vector vecDest; AngleVectors(GetAbsAngles(), &vecDest); vecDest *= 200.0f; vecDest += GetAbsOrigin(); // Try the jump AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), vecDest, MASK_NPCSOLID, NULL, &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 255, 0, 0, 0, 5 ); } m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 0, 255, 0, 0, 5 ); } //Save this jump in case the next time fails m_vecSavedJump = moveTrace.vJumpVelocity; m_vecLastJumpAttempt = vecDest; SetSchedule(SCHED_ASW_ALIEN_JUMP); m_bForcedStuckJump = true; return true; }
//----------------------------------------------------------------------------- // Purpose: Handles movement towards the last move target. // Input : flInterval - //----------------------------------------------------------------------------- bool CNPC_Controller::OverridePathMove( float flInterval ) { CBaseEntity *pMoveTarget = (GetTarget()) ? GetTarget() : GetEnemy(); Vector waypointDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin(); float flWaypointDist = waypointDir.Length2D(); VectorNormalize(waypointDir); // cut corner? if (flWaypointDist < 128) { if (m_flGroundSpeed > 100) m_flGroundSpeed -= 40; } else { if (m_flGroundSpeed < 400) m_flGroundSpeed += 10; } m_velocity = m_velocity * 0.8 + m_flGroundSpeed * waypointDir * 0.5; SetAbsVelocity( m_velocity ); // ----------------------------------------------------------------- // Check route is blocked // ------------------------------------------------------------------ Vector checkPos = GetLocalOrigin() + (waypointDir * (m_flGroundSpeed * flInterval)); AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), checkPos, MASK_NPCSOLID|CONTENTS_WATER, pMoveTarget, &moveTrace); if (IsMoveBlocked( moveTrace )) { TaskFail(FAIL_NO_ROUTE); GetNavigator()->ClearGoal(); return true; } // ---------------------------------------------- Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin(); if ( ProgressFlyPath( flInterval, pMoveTarget, MASK_NPCSOLID, false, 64 ) == AINPP_COMPLETE ) { { m_vLastPatrolDir = lastPatrolDir; VectorNormalize(m_vLastPatrolDir); } return true; } return false; }
AIMotorMoveResult_t CAI_Motor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult ) { // turn in the direction of movement MoveFacing( move ); // calc accel/decel rates float flNewSpeed = GetIdealSpeed(); SetMoveVel( move.dir * flNewSpeed ); float flTotal = 0.5 * (GetCurSpeed() + flNewSpeed) * GetMoveInterval(); float distance = move.maxDist; // can I move farther in this interval than I'm supposed to? if (flTotal > distance) { // only use a portion of the time interval SetMoveInterval( GetMoveInterval() * (1 - distance / flTotal) ); flTotal = distance; } else { // use all the time SetMoveInterval( 0 ); } Vector vecStart, vecEnd; vecStart = GetLocalOrigin(); VectorMA( vecStart, flTotal, move.dir, vecEnd ); AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_FLY, vecStart, vecEnd, MASK_NPCSOLID, NULL, &moveTrace ); if ( pTraceResult ) *pTraceResult = moveTrace; // Check for total blockage if (fabs(moveTrace.flDistObstructed - flTotal) <= 1e-1) { // But if we bumped into our target, then we succeeded! if ( move.pMoveTarget && (moveTrace.pObstruction == move.pMoveTarget) ) return AIM_PARTIAL_HIT_TARGET; return AIM_FAILED; } // The true argument here causes it to touch all triggers // in the volume swept from the previous position to the current position UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true); return (IsMoveBlocked(moveTrace.fStatus)) ? AIM_PARTIAL_HIT_WORLD : AIM_SUCCESS; }
bool CASW_Alien_Jumper::DoJumpTo(Vector &vecDest) { //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; // only jump if you're on the ground if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP ) return false; // Don't jump if I'm not allowed if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false ) return false; // Try the jump AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), vecDest, MASK_NPCSOLID, NULL, &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 255, 0, 0, 0, 5 ); } m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( vecDest, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), vecDest, 0, 255, 0, 0, 5 ); } //Save this jump in case the next time fails m_vecSavedJump = moveTrace.vJumpVelocity; m_vecLastJumpAttempt = vecDest; SetSchedule(SCHED_ASW_ALIEN_JUMP); m_bForcedStuckJump = true; //Msg("Drone saving jump vec %f %f %f\n", m_vecSavedJump.x, m_vecSavedJump.y, m_vecSavedJump.z); return true; }
//----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CASW_Alien_Jumper::ShouldJump( void ) { if ( GetEnemy() == NULL ) return false; // don't jump if we're stunned if (m_bElectroStunned) return false; //Too soon to try to jump if ( m_flJumpTime > gpGlobals->curtime ) return false; // only jump if you're on the ground if (!(GetFlags() & FL_ONGROUND) || GetNavType() == NAV_JUMP ) return false; // Don't jump if I'm not allowed if ( ( CapabilitiesGet() & bits_CAP_MOVE_JUMP ) == false ) return false; Vector vEnemyForward, vForward; GetEnemy()->GetVectors( &vEnemyForward, NULL, NULL ); GetVectors( &vForward, NULL, NULL ); float flDot = DotProduct( vForward, vEnemyForward ); if ( flDot < 0.5f ) flDot = 0.5f; Vector vecPredictedPos; //Get our likely position in two seconds UTIL_PredictedPosition( GetEnemy(), flDot * 2.5f, &vecPredictedPos ); // Don't jump if we're already near the target if ( ( GetAbsOrigin() - vecPredictedPos ).LengthSqr() < (512*512) ) return false; //Don't retest if the target hasn't moved enough //FIXME: Check your own distance from last attempt as well if ( ( ( m_vecLastJumpAttempt - vecPredictedPos ).LengthSqr() ) < (128*128) ) { m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } Vector targetDir = ( vecPredictedPos - GetAbsOrigin() ); float flDist = VectorNormalize( targetDir ); // don't jump at target it it's very close if (flDist < ANTLION_JUMP_MIN) return false; Vector targetPos = vecPredictedPos + ( targetDir * (GetHullWidth()*4.0f) ); // Try the jump AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_JUMP, GetAbsOrigin(), targetPos, MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), targetPos, 255, 0, 0, 0, 5 ); } m_flJumpTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 2.0f ); return false; } if ( asw_debug_aliens.GetInt() == 2 ) { NDebugOverlay::Box( targetPos, GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 5 ); NDebugOverlay::Line( GetAbsOrigin(), targetPos, 0, 255, 0, 0, 5 ); } //Save this jump in case the next time fails m_vecSavedJump = moveTrace.vJumpVelocity; m_vecLastJumpAttempt = targetPos; return true; }
//----------------------------------------------------------------------------- // Step iteratively toward a destination position //----------------------------------------------------------------------------- AIMotorMoveResult_t CAI_Motor::MoveGroundStep( const Vector &newPos, CBaseEntity *pMoveTarget, float yaw, bool bAsFarAsCan, bool bTestZ, AIMoveTrace_t *pTraceResult ) { // By definition, this will produce different results than GroundMoveLimit() // because there's no guarantee that it will step exactly one step // See how far toward the new position we can step... // But don't actually test for ground geometric validity; // if it isn't valid, there's not much we can do about it AIMoveTrace_t moveTrace; unsigned testFlags = AITGM_IGNORE_FLOOR; char *pchHackBoolToInt = (char*)(&bTestZ); if ( *pchHackBoolToInt == 2 ) { testFlags |= AITGM_CRAWL_LARGE_STEPS; } else { if ( !bTestZ ) testFlags |= AITGM_2D; } #ifdef DEBUG if ( ai_draw_motor_movement.GetBool() ) testFlags |= AITGM_DRAW_RESULTS; #endif GetMoveProbe()->TestGroundMove( GetLocalOrigin(), newPos, GetOuter()->GetAITraceMask(), testFlags, &moveTrace ); if ( pTraceResult ) { *pTraceResult = moveTrace; } bool bHitTarget = (moveTrace.pObstruction && (pMoveTarget == moveTrace.pObstruction )); // Move forward either if there was no obstruction or if we're told to // move as far as we can, regardless bool bIsBlocked = IsMoveBlocked(moveTrace.fStatus); if ( !bIsBlocked || bAsFarAsCan || bHitTarget ) { #ifdef DEBUG if ( GetMoveProbe()->CheckStandPosition( GetLocalOrigin(), GetOuter()->GetAITraceMask() ) && !GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, GetOuter()->GetAITraceMask() ) ) { DevMsg( 2, "Warning: AI motor probably given invalid instructions\n" ); } #endif // The true argument here causes it to touch all triggers // in the volume swept from the previous position to the current position UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true); // check to see if our ground entity has changed // NOTE: This is to detect changes in ground entity as the movement code has optimized out // ground checks. So now we have to do a simple recheck to make sure we detect when we've // stepped onto a new entity. if ( GetOuter()->GetFlags() & FL_ONGROUND ) { GetOuter()->PhysicsStepRecheckGround(); } // skip tiny steps, but notify the shadow object of any large steps if ( moveTrace.flStepUpDistance > 0.1f ) { float height = clamp( moveTrace.flStepUpDistance, 0, StepHeight() ); IPhysicsObject *pPhysicsObject = GetOuter()->VPhysicsGetObject(); if ( pPhysicsObject ) { IPhysicsShadowController *pShadow = pPhysicsObject->GetShadowController(); if ( pShadow ) { pShadow->StepUp( height ); } } } if ( yaw != -1 ) { QAngle angles = GetLocalAngles(); angles.y = yaw; SetLocalAngles( angles ); } if ( bHitTarget ) return AIM_PARTIAL_HIT_TARGET; if ( !bIsBlocked ) return AIM_SUCCESS; if ( moveTrace.fStatus == AIMR_BLOCKED_NPC ) return AIM_PARTIAL_HIT_NPC; return AIM_PARTIAL_HIT_WORLD; } return AIM_FAILED; }
//----------------------------------------------------------------------------- // Purpose: Handles all flight movement. // Input : flInterval - Seconds to simulate. //----------------------------------------------------------------------------- void CNPC_Crow::MoveCrowFly( float flInterval ) { // // Bound interval so we don't get ludicrous motion when debugging // or when framerate drops catastrophically. // if (flInterval > 1.0) { flInterval = 1.0; } m_flDangerSoundTime = gpGlobals->curtime + 5.0f; // // Determine the goal of our movement. // Vector vecMoveGoal = GetAbsOrigin(); if ( GetNavigator()->IsGoalActive() ) { vecMoveGoal = GetNavigator()->GetCurWaypointPos(); if ( GetNavigator()->CurWaypointIsGoal() == false ) { AI_ProgressFlyPathParams_t params( MASK_NPCSOLID ); params.bTrySimplify = false; GetNavigator()->ProgressFlyPath( params ); // ignore result, crow handles completion directly // Fly towards the hint. if ( GetNavigator()->GetPath()->GetCurWaypoint() ) { vecMoveGoal = GetNavigator()->GetCurWaypointPos(); } } } else { // No movement goal. vecMoveGoal = GetAbsOrigin(); SetAbsVelocity( vec3_origin ); return; } Vector vecMoveDir = ( vecMoveGoal - GetAbsOrigin() ); Vector vForward; AngleVectors( GetAbsAngles(), &vForward ); // // Fly towards the movement goal. // float flDistance = ( vecMoveGoal - GetAbsOrigin() ).Length(); if ( vecMoveGoal != m_vDesiredTarget ) { m_vDesiredTarget = vecMoveGoal; } else { m_vCurrentTarget = ( m_vDesiredTarget - GetAbsOrigin() ); VectorNormalize( m_vCurrentTarget ); } float flLerpMod = 0.25f; if ( flDistance <= 256.0f ) { flLerpMod = 1.0f - ( flDistance / 256.0f ); } VectorLerp( vForward, m_vCurrentTarget, flLerpMod, vForward ); if ( flDistance < CROW_AIRSPEED * flInterval ) { if ( GetNavigator()->IsGoalActive() ) { if ( GetNavigator()->CurWaypointIsGoal() ) { m_bReachedMoveGoal = true; } else { GetNavigator()->AdvancePath(); } } else m_bReachedMoveGoal = true; } if ( GetHintNode() ) { AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_FLY, GetAbsOrigin(), GetNavigator()->GetCurWaypointPos(), MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace ); //See if it succeeded if ( IsMoveBlocked( moveTrace.fStatus ) ) { Vector vNodePos = vecMoveGoal; GetHintNode()->GetPosition(this, &vNodePos); GetNavigator()->SetGoal( vNodePos ); } } // // Look to see if we are going to hit anything. // VectorNormalize( vForward ); Vector vecDeflect; if ( Probe( vForward, CROW_AIRSPEED * flInterval, vecDeflect ) ) { vForward = vecDeflect; VectorNormalize( vForward ); } SetAbsVelocity( vForward * CROW_AIRSPEED ); if ( GetAbsVelocity().Length() > 0 && GetNavigator()->CurWaypointIsGoal() && flDistance < CROW_AIRSPEED ) { SetIdealActivity( (Activity)ACT_CROW_LAND ); } //Bank and set angles. Vector vRight; QAngle vRollAngle; VectorAngles( vForward, vRollAngle ); vRollAngle.z = 0; AngleVectors( vRollAngle, NULL, &vRight, NULL ); float flRoll = DotProduct( vRight, vecMoveDir ) * 45; flRoll = clamp( flRoll, -45, 45 ); vRollAngle[ROLL] = flRoll; SetAbsAngles( vRollAngle ); }
//----------------------------------------------------------------------------- // Purpose: // Input : right - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_Assassin::CanFlip( int flipType, Activity &activity, const Vector *avoidPosition ) { Vector testDir; Activity act = ACT_INVALID; switch( flipType ) { case FLIP_RIGHT: GetVectors( NULL, &testDir, NULL ); act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_RIGHT ); break; case FLIP_LEFT: GetVectors( NULL, &testDir, NULL ); testDir.Negate(); act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_LEFT ); break; case FLIP_FORWARD: GetVectors( &testDir, NULL, NULL ); act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_FORWARD ); break; case FLIP_BACKWARD: GetVectors( &testDir, NULL, NULL ); testDir.Negate(); act = NPC_TranslateActivity( (Activity) ACT_ASSASSIN_FLIP_BACK ); break; default: assert(0); //NOTENOTE: Invalid flip type activity = ACT_INVALID; return false; break; } // Make sure we don't flip towards our avoidance position/ if ( avoidPosition != NULL ) { Vector avoidDir = (*avoidPosition) - GetAbsOrigin(); VectorNormalize( avoidDir ); if ( DotProduct( avoidDir, testDir ) > 0.0f ) return false; } int seq = SelectWeightedSequence( act ); // Find out the length of this sequence float testDist = GetSequenceMoveDist( seq ); // Find the resulting end position from the sequence's movement Vector endPos = GetAbsOrigin() + ( testDir * testDist ); trace_t tr; if ( ( flipType != FLIP_BACKWARD ) && ( avoidPosition != NULL ) ) { UTIL_TraceLine( (*avoidPosition), endPos, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction == 1.0f ) return false; } /* UTIL_TraceHull( GetAbsOrigin(), endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); // See if we're hit an obstruction in that direction if ( tr.fraction < 1.0f ) { if ( g_debug_assassin.GetBool() ) { NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 255, 0, 0, true, 2.0f ); } return false; } #define NUM_STEPS 2 float stepLength = testDist / NUM_STEPS; for ( int i = 1; i <= NUM_STEPS; i++ ) { endPos = GetAbsOrigin() + ( testDir * (stepLength*i) ); // Also check for a cliff edge UTIL_TraceHull( endPos, endPos - Vector( 0, 0, StepHeight() * 4.0f ), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction == 1.0f ) { if ( g_debug_assassin.GetBool() ) { NDebugOverlay::BoxDirection( endPos, NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( StepHeight() * 4.0f, 0, StepHeight() ), Vector(0,0,-1), 255, 0, 0, true, 2.0f ); } return false; } } if ( g_debug_assassin.GetBool() ) { NDebugOverlay::BoxDirection( GetAbsOrigin(), NAI_Hull::Mins(m_eHull) + Vector( 0, 0, StepHeight() ), NAI_Hull::Maxs(m_eHull) + Vector( testDist, 0, StepHeight() ), testDir, 0, 255, 0, true, 2.0f ); } */ AIMoveTrace_t moveTrace; GetMoveProbe()->TestGroundMove( GetAbsOrigin(), endPos, MASK_NPCSOLID, AITGM_DEFAULT, &moveTrace ); if ( moveTrace.fStatus != AIMR_OK ) return false; // Return the activity to use activity = (Activity) act; return true; }
bool CAI_LocalNavigator::MoveCalcDirect( AILocalMoveGoal_t *pMoveGoal, bool bOnlyCurThink, float *pDistClear, AIMoveResult_t *pResult ) { AI_PROFILE_SCOPE(CAI_LocalNavigator_MoveCalcDirect); bool bRetVal = false; if ( pMoveGoal->speed ) { CAI_Motor *pMotor = GetOuter()->GetMotor(); float minCheckDist = pMotor->MinCheckDist(); float probeDist = m_pPlaneSolver->CalcProbeDist( pMoveGoal->speed ); // having this match steering allows one fewer traces float checkDist = MAX( minCheckDist, probeDist ); float checkStepDist = MAX( 16.0, probeDist * 0.5 ); if ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) { // clamp checkDist to be no farther than MAX distance to goal checkDist = MIN( checkDist, pMoveGoal->maxDist ); } if ( checkDist <= 0.0 ) { *pResult = AIMR_OK; return true; } float moveThisInterval = pMotor->CalcIntervalMove(); bool bExpectingArrival = (moveThisInterval >= checkDist); if ( !m_FullDirectTimer.Expired() ) { if ( !m_fLastWasClear || ( !VectorsAreEqual(pMoveGoal->target, m_LastMoveGoal.target, 0.1) || !VectorsAreEqual(pMoveGoal->dir, m_LastMoveGoal.dir, 0.1) ) || bExpectingArrival ) { m_FullDirectTimer.Force(); } } if ( bOnlyCurThink ) // Outer code claims to have done a validation (probably a simplify operation) { m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] ); } // First, check the probable move for this cycle bool bTraceClear = true; Vector testPos; if ( !bExpectingArrival ) { testPos = GetLocalOrigin() + pMoveGoal->dir * moveThisInterval; bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, GetOuter()->GetAITraceMask(), pMoveGoal->pMoveTarget, 100.0, ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, &pMoveGoal->directTrace ); if ( !bTraceClear ) { // Adjust probe top match expected probe dist (relied on later in process) pMoveGoal->directTrace.flDistObstructed = (checkDist - moveThisInterval) + pMoveGoal->directTrace.flDistObstructed; } if ( !IsRetail() && ai_debug_directnavprobe.GetBool() ) { if ( !bTraceClear ) { DevMsg( GetOuter(), "Close obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed ); NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 255, 0, 0, false, 0.1 ); if ( pMoveGoal->directTrace.pObstruction ) NDebugOverlay::Line( WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 255, 0, 255, false, 0.1 ); } else { NDebugOverlay::Line( WorldSpaceCenter(), Vector( testPos.x, testPos.y, WorldSpaceCenter().z ), 0, 255, 0, false, 0.1 ); } } pMoveGoal->thinkTrace = pMoveGoal->directTrace; } // Now project out for future obstructions if ( bTraceClear ) { if ( m_FullDirectTimer.Expired() ) { testPos = GetLocalOrigin() + pMoveGoal->dir * checkDist; float checkStepPct = (checkStepDist / checkDist) * 100.0; if ( checkStepPct > 100.0 ) checkStepPct = 100.0; bTraceClear = GetMoveProbe()->MoveLimit( pMoveGoal->navType, GetLocalOrigin(), testPos, GetOuter()->GetAITraceMask(), pMoveGoal->pMoveTarget, checkStepPct, ( pMoveGoal->navType == NAV_GROUND ) ? AIMLF_2D : AIMLF_DEFAULT, &pMoveGoal->directTrace ); if ( bExpectingArrival ) pMoveGoal->thinkTrace = pMoveGoal->directTrace; if (ai_debug_directnavprobe.GetBool() ) { if ( !bTraceClear ) { NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 255, 0, 0, false, 0.1 ); DevMsg( GetOuter(), "Obstruction %f\n", checkDist - pMoveGoal->directTrace.flDistObstructed ); } else { NDebugOverlay::Line( GetOuter()->EyePosition(), Vector( testPos.x, testPos.y, GetOuter()->EyePosition().z ), 0, 255, 0, false, 0.1 ); DevMsg( GetOuter(), "No obstruction\n" ); } } } else { if ( ai_debug_directnavprobe.GetBool() ) DevMsg( GetOuter(), "No obstruction (Near probe only)\n" ); } } pMoveGoal->bHasTraced = true; float distClear = checkDist - pMoveGoal->directTrace.flDistObstructed; if (distClear < 0.001) distClear = 0; if ( bTraceClear ) { *pResult = AIMR_OK; bRetVal = true; m_fLastWasClear = true; } else if ( ( pMoveGoal->flags & ( AILMG_TARGET_IS_TRANSITION | AILMG_TARGET_IS_GOAL ) ) && pMoveGoal->maxDist < distClear ) { *pResult = AIMR_OK; bRetVal = true; m_fLastWasClear = true; } else { *pDistClear = distClear; m_fLastWasClear = false; } } else { // Should never end up in this function with speed of zero. Probably an activity problem. *pResult = AIMR_ILLEGAL; bRetVal = true; } m_LastMoveGoal = *pMoveGoal; if ( bRetVal && m_FullDirectTimer.Expired() ) m_FullDirectTimer.Set( TIME_DELAY_FULL_DIRECT_PROBE[AIStrongOpt()] ); return bRetVal; }
AIMotorMoveResult_t CAI_BlendedMotor::MoveFlyExecute( const AILocalMoveGoal_t &move, AIMoveTrace_t *pTraceResult ) { if ( move.curExpectedDist < 0.001 ) return BaseClass::MoveFlyExecute( move, pTraceResult ); BuildMoveScript( move, pTraceResult ); float flNewSpeed = GetCurSpeed(); float flTotalDist = GetMoveScriptDist( flNewSpeed ); Assert( move.maxDist < 0.01 || flTotalDist > 0.0 ); // -------------------------------------------- // turn in the direction of movement // -------------------------------------------- float flNewYaw = GetMoveScriptYaw( ); // get facing based on movement yaw AILocalMoveGoal_t move2 = move; move2.facing = UTIL_YawToVector( flNewYaw ); // turn in the direction needed MoveFacing( move2 ); GetOuter()->m_flGroundSpeed = GetSequenceGroundSpeed( GetSequence()); SetMoveScriptAnim( flNewSpeed ); // DevMsg( "%6.2f : Speed %.1f : %.1f\n", gpGlobals->curtime, flNewSpeed, GetIdealSpeed() ); // reset actual "sequence" ground speed based current movement sequence, orientation // FIXME: the above is redundant with MoveGroundExecute, and the below is a mix of MoveGroundExecuteWalk and MoveFlyExecute bool bReachingLocalGoal = ( flTotalDist > move.maxDist ); // can I move farther in this interval than I'm supposed to? if ( bReachingLocalGoal ) { if ( !(move.flags & AILMG_CONSUME_INTERVAL) ) { // only use a portion of the time interval SetMoveInterval( GetMoveInterval() * (1 - move.maxDist / flTotalDist) ); } else SetMoveInterval( 0 ); flTotalDist = move.maxDist; } else { // use all the time SetMoveInterval( 0 ); } SetMoveVel( move.dir * flNewSpeed ); // orig Vector vecStart, vecEnd; vecStart = GetLocalOrigin(); VectorMA( vecStart, flTotalDist, move.dir, vecEnd ); AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_FLY, vecStart, vecEnd, MASK_NPCSOLID, NULL, &moveTrace ); if ( pTraceResult ) *pTraceResult = moveTrace; // Check for total blockage if (fabs(moveTrace.flDistObstructed - flTotalDist) <= 1e-1) { // But if we bumped into our target, then we succeeded! if ( move.pMoveTarget && (moveTrace.pObstruction == move.pMoveTarget) ) return AIM_PARTIAL_HIT_TARGET; return AIM_FAILED; } // The true argument here causes it to touch all triggers // in the volume swept from the previous position to the current position UTIL_SetOrigin(GetOuter(), moveTrace.vEndPosition, true); return (IsMoveBlocked(moveTrace.fStatus)) ? AIM_PARTIAL_HIT_WORLD : AIM_SUCCESS; }
void CAI_BlendedMotor::BuildVelocityScript( const AILocalMoveGoal_t &move ) { int i; float a; float idealVelocity = GetIdealSpeed(); if (idealVelocity == 0) { idealVelocity = 50; } float idealAccel = GetIdealAccel(); if (idealAccel == 0) { idealAccel = 100; } AI_Movementscript_t script; // set current location as start of script script.vecLocation = GetAbsOrigin(); script.flMaxVelocity = GetCurSpeed(); m_scriptMove.AddToTail( script ); //------------------------- extern ConVar *npc_height_adjust; if (npc_height_adjust->GetBool() && move.bHasTraced && move.directTrace.flTotalDist != move.thinkTrace.flTotalDist) { float flDist = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D(); float flHeight = move.directTrace.vEndPosition.z - m_scriptMove[0].vecLocation.z; float flDelta; if (flDist > 0) { flDelta = flHeight / flDist; } else { flDelta = 0; } m_flPredictiveSpeedAdjust = 1.1 - fabs( flDelta ); m_flPredictiveSpeedAdjust = clamp( m_flPredictiveSpeedAdjust, (flHeight > 0.0) ? 0.5 : 0.8, 1.0 ); /* if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) { Msg("m_flPredictiveSpeedAdjust %.3f %.1f %.1f\n", m_flPredictiveSpeedAdjust, flHeight, flDist ); NDebugOverlay::Box( move.directTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0,255,255, 0, 0.12 ); } */ } if (npc_height_adjust->GetBool()) { float flDist = (move.thinkTrace.vEndPosition - m_vecPrevOrigin2).Length2D(); float flHeight = move.thinkTrace.vEndPosition.z - m_vecPrevOrigin2.z; float flDelta; if (flDist > 0) { flDelta = flHeight / flDist; } else { flDelta = 0; } float newSpeedAdjust = 1.1 - fabs( flDelta ); newSpeedAdjust = clamp( newSpeedAdjust, (flHeight > 0.0) ? 0.5 : 0.8, 1.0 ); // debounce speed adjust if (newSpeedAdjust < m_flReactiveSpeedAdjust) { m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.2 + newSpeedAdjust * 0.8; } else { m_flReactiveSpeedAdjust = m_flReactiveSpeedAdjust * 0.5 + newSpeedAdjust * 0.5; } // filter through origins m_vecPrevOrigin2 = m_vecPrevOrigin1; m_vecPrevOrigin1 = GetAbsOrigin(); /* if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) { NDebugOverlay::Box( m_vecPrevOrigin2, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 ); NDebugOverlay::Box( move.thinkTrace.vEndPosition, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,255, 0, 0.12 ); Msg("m_flReactiveSpeedAdjust %.3f %.1f %.1f\n", m_flReactiveSpeedAdjust, flHeight, flDist ); } */ } idealVelocity = idealVelocity * min( m_flReactiveSpeedAdjust, m_flPredictiveSpeedAdjust ); //------------------------- bool bAddedExpected = false; // add all waypoint locations and velocities AI_Waypoint_t *pCurWaypoint = GetNavigator()->GetPath()->GetCurWaypoint(); // there has to be at least one waypoint Assert( pCurWaypoint ); while (pCurWaypoint && (pCurWaypoint->NavType() == NAV_GROUND || pCurWaypoint->NavType() == NAV_FLY) /*&& flTotalDist / idealVelocity < 3.0*/) // limit lookahead to 3 seconds { script.Init(); AI_Waypoint_t *pNext = pCurWaypoint->GetNext(); if (ai_path_adjust_speed_on_immediate_turns->GetBool() && !bAddedExpected) { // hack in next expected immediate location for move script.vecLocation = GetAbsOrigin() + move.dir * move.curExpectedDist; bAddedExpected = true; pNext = pCurWaypoint; } else { script.vecLocation = pCurWaypoint->vecLocation; script.pWaypoint = pCurWaypoint; } //DevMsg("waypoint %.1f %.1f %.1f\n", script.vecLocation.x, script.vecLocation.y, script.vecLocation.z ); if (pNext) { switch( pNext->NavType()) { case NAV_GROUND: case NAV_FLY: { Vector d1 = pNext->vecLocation - script.vecLocation; Vector d2 = script.vecLocation - m_scriptMove[m_scriptMove.Count()-1].vecLocation; // remove very short, non terminal ground links // FIXME: is this safe? Maybe just check for co-located ground points? if (d1.Length2D() < 1.0) { /* if (m_scriptMove.Count() > 1) { int i = m_scriptMove.Count() - 1; m_scriptMove[i].vecLocation = pCurWaypoint->vecLocation; m_scriptMove[i].pWaypoint = pCurWaypoint; } */ pCurWaypoint = pNext; continue; } d1.z = 0; VectorNormalize( d1 ); d2.z = 0; VectorNormalize( d2 ); // figure velocity float dot = (DotProduct( d1, d2 ) + 0.2); if (dot > 0) { dot = clamp( dot, 0.0f, 1.0f ); script.flMaxVelocity = idealVelocity * dot; } else { script.flMaxVelocity = 0; } } break; case NAV_JUMP: // FIXME: information about what the jump should look like isn't stored in the waypoints // this'll need to call // GetMoveProbe()->MoveLimit( NAV_JUMP, GetLocalOrigin(), GetPath()->CurWaypointPos(), MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace ); // to get how far/fast the jump will be, but this is also stateless, so it'd call it per frame. // So far it's not clear that the moveprobe doesn't also call this..... { float minJumpHeight = 0; float maxHorzVel = max( GetCurSpeed(), 100 ); float gravity = sv_gravity->GetFloat() * GetOuter()->GetGravity(); Vector vecApex; Vector rawJumpVel = GetMoveProbe()->CalcJumpLaunchVelocity(script.vecLocation, pNext->vecLocation, gravity, &minJumpHeight, maxHorzVel, &vecApex ); script.flMaxVelocity = rawJumpVel.Length2D(); // Msg("%.1f\n", script.flMaxVelocity ); } break; case NAV_CLIMB: { /* CAI_Node *pClimbNode = GetNavigator()->GetNetwork()->GetNode(pNext->iNodeID); check: pClimbNode->m_eNodeInfo bits_NODE_CLIMB_BOTTOM, bits_NODE_CLIMB_ON, bits_NODE_CLIMB_OFF_FORWARD, bits_NODE_CLIMB_OFF_LEFT, bits_NODE_CLIMB_OFF_RIGHT */ script.flMaxVelocity = 0; } break; /* case NAV_FLY: // FIXME: can there be a NAV_GROUND -> NAV_FLY transition? script.flMaxVelocity = 0; break; */ default: break; } } else { script.flMaxVelocity = GetNavigator()->GetArrivalSpeed(); // Assert( script.flMaxVelocity == 0 ); } m_scriptMove.AddToTail( script ); pCurWaypoint = pNext; } //------------------------- // update distances float flTotalDist = 0; for (i = 0; i < m_scriptMove.Count() - 1; i++ ) { flTotalDist += m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D(); } //------------------------- if ( !m_bDeceleratingToGoal && m_scriptMove.Count() && flTotalDist > 0 ) { float flNeededAccel = DeltaV( m_scriptMove[0].flMaxVelocity, m_scriptMove[m_scriptMove.Count() - 1].flMaxVelocity, flTotalDist ); m_bDeceleratingToGoal = (flNeededAccel < -idealAccel); //Assert( flNeededAccel != idealAccel); } //------------------------- // insert slowdown points due to blocking if (ai_path_insert_pause_at_obstruction->GetBool() && move.directTrace.pObstruction) { float distToObstruction = (move.directTrace.vEndPosition - m_scriptMove[0].vecLocation).Length2D(); // HACK move obstruction out "stepsize" to account for it being based on stand position and not a trace distToObstruction = distToObstruction + 16; InsertSlowdown( distToObstruction, idealAccel, false ); } if (ai_path_insert_pause_at_est_end->GetBool() && GetNavigator()->GetArrivalDistance() > 0.0) { InsertSlowdown( flTotalDist - GetNavigator()->GetArrivalDistance(), idealAccel, true ); } // calc initial velocity based on immediate direction changes if ( ai_path_adjust_speed_on_immediate_turns->GetBool() && m_scriptMove.Count() > 1) { /* if ((GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT)) { Vector tmp = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation; VectorNormalize( tmp ); NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 255,255,255, true, 0.1 ); NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[1].vecLocation + Vector( 0, 0, 10 ), 255,0,0, true, 0.1 ); tmp = GetCurVel(); VectorNormalize( tmp ); NDebugOverlay::Line( m_scriptMove[0].vecLocation + Vector( 0, 0, 10 ), m_scriptMove[0].vecLocation + tmp * 32 + Vector( 0, 0, 10 ), 0,0,255, true, 0.1 ); } */ Vector d1 = m_scriptMove[1].vecLocation - m_scriptMove[0].vecLocation; d1.z = 0; VectorNormalize( d1 ); Vector d2 = GetCurVel(); d2.z = 0; VectorNormalize( d2 ); float dot = (DotProduct( d1, d2 ) + MIN_STEER_DOT); dot = clamp( dot, 0.0f, 1.0f ); m_scriptMove[0].flMaxVelocity = m_scriptMove[0].flMaxVelocity * dot; } // clamp forward velocities for (i = 0; i < m_scriptMove.Count() - 1; i++ ) { // find needed acceleration float dv = m_scriptMove[i+1].flMaxVelocity - m_scriptMove[i].flMaxVelocity; if (dv > 0.0) { // find time, distance to accel to next max vel float t1 = dv / idealAccel; float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1; // is there enough distance if (d1 > m_scriptMove[i].flDist) { float r1, r2; // clamp the next velocity to the possible accel in the given distance if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i].flDist, r1, r2 )) { m_scriptMove[i+1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1; } } } } // clamp decel velocities for (i = m_scriptMove.Count() - 1; i > 0; i-- ) { // find needed deceleration float dv = m_scriptMove[i].flMaxVelocity - m_scriptMove[i-1].flMaxVelocity; if (dv < 0.0) { // find time, distance to decal to next max vel float t1 = -dv / idealAccel; float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1; // is there enough distance if (d1 > m_scriptMove[i-1].flDist) { float r1, r2; // clamp the next velocity to the possible decal in the given distance if (SolveQuadratic( 0.5 * idealAccel, m_scriptMove[i].flMaxVelocity, -m_scriptMove[i-1].flDist, r1, r2 )) { m_scriptMove[i-1].flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * r1; } } } } /* for (i = 0; i < m_scriptMove.Count(); i++) { NDebugOverlay::Text( m_scriptMove[i].vecLocation, (const char *)CFmtStr( "%.2f ", m_scriptMove[i].flMaxVelocity ), false, 0.1 ); // DevMsg("%.2f ", m_scriptMove[i].flMaxVelocity ); } // DevMsg("\n"); */ // insert intermediate ideal velocities for (i = 0; i < m_scriptMove.Count() - 1;) { // accel to ideal float t1 = (idealVelocity - m_scriptMove[i].flMaxVelocity) / idealAccel; float d1 = m_scriptMove[i].flMaxVelocity * t1 + 0.5 * (idealAccel) * t1 * t1; // decel from ideal float t2 = (idealVelocity - m_scriptMove[i+1].flMaxVelocity) / idealAccel; float d2 = m_scriptMove[i+1].flMaxVelocity * t2 + 0.5 * (idealAccel) * t2 * t2; m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D(); // is it possible to accel and decal to idealVelocity between next two nodes if (d1 + d2 < m_scriptMove[i].flDist) { Vector start = m_scriptMove[i].vecLocation; Vector end = m_scriptMove[i+1].vecLocation; float dist = m_scriptMove[i].flDist; // insert the two points needed to end accel and start decel if (d1 > 1.0 && t1 > 0.1) { a = d1 / dist; script.Init(); script.vecLocation = end * a + start * (1 - a); script.flMaxVelocity = idealVelocity; m_scriptMove.InsertAfter( i, script ); i++; } if (dist - d2 > 1.0 && t2 > 0.1) { // DevMsg("%.2f : ", a ); a = (dist - d2) / dist; script.Init(); script.vecLocation = end * a + start * (1 - a); script.flMaxVelocity = idealVelocity; m_scriptMove.InsertAfter( i, script ); i++; } i++; } else { // check to see if the amount of change needed to reach target is less than the ideal acceleration float flNeededAccel = fabs( DeltaV( m_scriptMove[i].flMaxVelocity, m_scriptMove[i+1].flMaxVelocity, m_scriptMove[i].flDist ) ); if (flNeededAccel < idealAccel) { // if so, they it's possible to get a bit towards the ideal velocity float v1 = m_scriptMove[i].flMaxVelocity; float v2 = m_scriptMove[i+1].flMaxVelocity; float dist = m_scriptMove[i].flDist; // based on solving: // v1+A*t1-v2-A*t2=0 // v1*t1+0.5*A*t1*t1+v2*t2+0.5*A*t2*t2-D=0 float tmp = idealAccel*dist+0.5*v1*v1+0.5*v2*v2; Assert( tmp >= 0 ); t1 = (-v1+sqrt( tmp )) / idealAccel; t2 = (v1+idealAccel*t1-v2)/idealAccel; // if this assert hits, write down the v1, v2, dist, and idealAccel numbers and send them to me (Ken). // go ahead the comment it out, it's safe, but I'd like to know a test case where it's happening //Assert( t1 > 0 && t2 > 0 ); // check to make sure it's really worth it if (t1 > 0.0 && t2 > 0.0) { d1 = v1 * t1 + 0.5 * idealAccel * t1 * t1; /* d2 = v2 * t2 + 0.5 * idealAccel * t2 * t2; Assert( fabs( d1 + d2 - dist ) < 0.001 ); */ float a = d1 / m_scriptMove[i].flDist; script.Init(); script.vecLocation = m_scriptMove[i+1].vecLocation * a + m_scriptMove[i].vecLocation * (1 - a); script.flMaxVelocity = m_scriptMove[i].flMaxVelocity + idealAccel * t1; if (script.flMaxVelocity < idealVelocity) { // DevMsg("insert %.2f %.2f %.2f\n", m_scriptMove[i].flMaxVelocity, script.flMaxVelocity, m_scriptMove[i+1].flMaxVelocity ); m_scriptMove.InsertAfter( i, script ); i += 1; } } } i += 1; } } // clamp min velocities for (i = 0; i < m_scriptMove.Count(); i++) { m_scriptMove[i].flMaxVelocity = max( m_scriptMove[i].flMaxVelocity, MIN_VELOCITY ); } // rebuild fields m_scriptMove[0].flElapsedTime = 0; for (i = 0; i < m_scriptMove.Count() - 1; ) { m_scriptMove[i].flDist = (m_scriptMove[i+1].vecLocation - m_scriptMove[i].vecLocation).Length2D(); if (m_scriptMove[i].flMaxVelocity == 0 && m_scriptMove[i+1].flMaxVelocity == 0) { // force a minimum velocity //CE_assert //Assert( 0 ); m_scriptMove[i+1].flMaxVelocity = 1.0; } float t = m_scriptMove[i].flDist / (0.5 * (m_scriptMove[i].flMaxVelocity + m_scriptMove[i+1].flMaxVelocity)); m_scriptMove[i].flTime = t; /* if (m_scriptMove[i].flDist < 0.01) { // Assert( m_scriptMove[i+1].pWaypoint == NULL ); m_scriptMove.Remove( i + 1 ); continue; } */ m_scriptMove[i+1].flElapsedTime = m_scriptMove[i].flElapsedTime + m_scriptMove[i].flTime; i++; } /* for (i = 0; i < m_scriptMove.Count(); i++) { DevMsg("(%.2f : %.2f : %.2f)", m_scriptMove[i].flMaxVelocity, m_scriptMove[i].flDist, m_scriptMove[i].flTime ); // DevMsg("(%.2f:%.2f)", m_scriptMove[i].flTime, m_scriptMove[i].flElapsedTime ); } DevMsg("\n"); */ }