// Immediately jump off of our ladder, if we're on one void CCSBot::GetOffLadder() { if (IsUsingLadder()) { Jump(MUST_JUMP); DestroyPath(); } }
// Check if we need to jump due to height change bool CHostageImprov::DiscontinuityJump(float ground, bool onlyJumpDown, bool mustJump) { // Don't try to jump if in the air or crouching. if (IsJumping() || IsCrouching() || IsUsingLadder()) return false; float dz = ground - GetFeet().z; if (dz > StepHeight && !onlyJumpDown) { Jump(); return true; } else if (dz < -JumpHeight) { Jump(); return true; } return false; }
bool CCSBot::DiscontinuityJump(float ground, bool onlyJumpDown, bool mustJump) { // don't try to jump again. if (m_isJumpCrouching) return false; float dz = ground - GetFeetZ(); if (dz > StepHeight && !onlyJumpDown) { // dont restrict jump time when going up if (Jump(MUST_JUMP)) { m_isJumpCrouching = true; m_isJumpCrouched = false; StandUp(); m_jumpCrouchTimestamp = gpGlobals->time; return true; } } else if (!IsUsingLadder() && dz < -JumpHeight) { if (Jump(mustJump)) { m_isJumpCrouching = true; m_isJumpCrouched = false; StandUp(); m_jumpCrouchTimestamp = gpGlobals->time; return true; } } return false; }
void CCSBot::StuckCheck() { if (m_isStuck) { // we are stuck - see if we have moved far enough to be considered unstuck Vector delta = pev->origin - m_stuckSpot; const float unstuckRange = 75.0f; if (delta.IsLengthGreaterThan(unstuckRange)) { // we are no longer stuck ResetStuckMonitor(); PrintIfWatched("UN-STUCK\n"); } } else { // check if we are stuck // compute average velocity over a short period (for stuck check) Vector vel = pev->origin - m_lastOrigin; // if we are jumping, ignore Z if (IsJumping()) vel.z = 0.0f; // cannot be Length2D, or will break ladder movement (they are only Z) float moveDist = vel.Length(); float deltaT = g_flBotFullThinkInterval; m_avgVel[ m_avgVelIndex++ ] = moveDist / deltaT; if (m_avgVelIndex == MAX_VEL_SAMPLES) m_avgVelIndex = 0; if (m_avgVelCount < MAX_VEL_SAMPLES) { m_avgVelCount++; } else { // we have enough samples to know if we're stuck float avgVel = 0.0f; for (int t = 0; t < m_avgVelCount; ++t) avgVel += m_avgVel[t]; avgVel /= m_avgVelCount; // cannot make this velocity too high, or bots will get "stuck" when going down ladders float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f; if (avgVel < stuckVel) { // we are stuck - note when and where we initially become stuck m_stuckTimestamp = gpGlobals->time; m_stuckSpot = pev->origin; m_stuckJumpTimestamp = gpGlobals->time + RANDOM_FLOAT(0.0f, 0.5f); PrintIfWatched("STUCK\n"); if (IsLocalPlayerWatchingMe() && cv_bot_debug.value > 0.0f) { EMIT_SOUND(ENT(pev), CHAN_ITEM, "buttons/button11.wav", VOL_NORM, ATTN_NORM); } m_isStuck = true; } } } // always need to track this m_lastOrigin = pev->origin; }
// Move actual view angles towards desired ones. // This is the only place v_angle is altered. // TODO: Make stiffness and turn rate constants timestep invariant. void CCSBot::UpdateLookAngles() { const float deltaT = g_flBotCommandInterval; float maxAccel; float stiffness; float damping; // springs are stiffer when attacking, so we can track and move between targets better if (IsAttacking()) { stiffness = 300.0f; damping = 30.0f; maxAccel = 3000.0f; } else { stiffness = 200.0f; damping = 25.0f; maxAccel = 3000.0f; } // these may be overridden by ladder logic float useYaw = m_lookYaw; float usePitch = m_lookPitch; // Ladders require precise movement, therefore we need to look at the // ladder as we approach and ascend/descend it. // If we are on a ladder, we need to look up or down to traverse it - override pitch in this case. // If we're trying to break something, though, we actually need to look at it before we can // look at the ladder if (IsUsingLadder()) { // set yaw to aim at ladder Vector to = m_pathLadder->m_top - pev->origin; float idealYaw = UTIL_VecToYaw(to); NavDirType faceDir = m_pathLadder->m_dir; if (m_pathLadderFaceIn) { faceDir = OppositeDirection(faceDir); } const float lookAlongLadderRange = 100.0f; const float ladderPitch = 60.0f; // adjust pitch to look up/down ladder as we ascend/descend switch (m_pathLadderState) { case APPROACH_ASCENDING_LADDER: { Vector to = m_goalPosition - pev->origin; useYaw = idealYaw; if (to.IsLengthLessThan(lookAlongLadderRange)) usePitch = -ladderPitch; break; } case APPROACH_DESCENDING_LADDER: { Vector to = m_goalPosition - pev->origin; useYaw = idealYaw; if (to.IsLengthLessThan(lookAlongLadderRange)) usePitch = ladderPitch; break; } case FACE_ASCENDING_LADDER: { useYaw = idealYaw; usePitch = -ladderPitch; break; } case FACE_DESCENDING_LADDER: { useYaw = idealYaw; usePitch = ladderPitch; break; } case MOUNT_ASCENDING_LADDER: case ASCEND_LADDER: { useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder); usePitch = -ladderPitch; break; } case MOUNT_DESCENDING_LADDER: case DESCEND_LADDER: { useYaw = DirectionToAngle(faceDir) + StayOnLadderLine(this, m_pathLadder); usePitch = ladderPitch; break; } case DISMOUNT_ASCENDING_LADDER: case DISMOUNT_DESCENDING_LADDER: { useYaw = DirectionToAngle(faceDir); break; } } } // Yaw float angleDiff = NormalizeAngle(useYaw - pev->v_angle.y); // if almost at target angle, snap to it const float onTargetTolerance = 1.0f; if (angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance) { m_lookYawVel = 0.0f; pev->v_angle.y = useYaw; } else { // simple angular spring/damper float accel = stiffness * angleDiff - damping * m_lookYawVel; // limit rate if (accel > maxAccel) accel = maxAccel; else if (accel < -maxAccel) accel = -maxAccel; m_lookYawVel += deltaT * accel; pev->v_angle.y += deltaT * m_lookYawVel; } // Pitch // Actually, this is negative pitch. angleDiff = usePitch - pev->v_angle.x; angleDiff = NormalizeAngle(angleDiff); if (false && angleDiff < onTargetTolerance && angleDiff > -onTargetTolerance) { m_lookPitchVel = 0.0f; pev->v_angle.x = usePitch; } else { // simple angular spring/damper // double the stiffness since pitch is only +/- 90 and yaw is +/- 180 float accel = 2.0f * stiffness * angleDiff - damping * m_lookPitchVel; // limit rate if (accel > maxAccel) accel = maxAccel; else if (accel < -maxAccel) accel = -maxAccel; m_lookPitchVel += deltaT * accel; pev->v_angle.x += deltaT * m_lookPitchVel; } // limit range - avoid gimbal lock if (pev->v_angle.x < -89.0f) pev->v_angle.x = -89.0f; else if (pev->v_angle.x > 89.0f) pev->v_angle.x = 89.0f; pev->v_angle.z = 0.0f; }
void CHostageImprov::MoveTowards(const Vector &pos, float deltaT) { Vector move; float_precision accelRate; const float crouchWalkRate = 250.0f; // Jump up on ledges // Because we may not be able to get to our goal position and enter the next // area because our extent collides with a nearby vertical ledge, make sure // we look far enough ahead to avoid this situation. // Can't look too far ahead, or bots will try to jump up slopes. // // NOTE: We need to do this frequently to catch edges at the right time // TODO: Look ahead *along path* instead of straight line ClearPath(); if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_NO_JUMP)) && !IsUsingLadder() && !IsJumping() && IsOnGround() && !IsCrouching()) { float ground; Vector aheadRay(pos.x - GetFeet().x, pos.y - GetFeet().y, 0); aheadRay.NormalizeInPlace(); bool jumped = false; if (IsRunning()) { const float farLookAheadRange = 80.0f; Vector normal; Vector stepAhead = GetFeet() + farLookAheadRange * aheadRay; stepAhead.z += HumanHeight; if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground, &normal )) { if (normal.z > 0.9f) jumped = DiscontinuityJump(ground, HOSTAGE_ONLY_JUMP_DOWN); } } if (!jumped) { // close up jumping // cant be less or will miss jumps over low walls const float lookAheadRange = 30.0f; Vector stepAhead = GetFeet() + lookAheadRange * aheadRay; stepAhead.z += HumanHeight; if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground)) { jumped = DiscontinuityJump(ground); } } if (!jumped) { // about to fall gap-jumping const float lookAheadRange = 10.0f; Vector stepAhead = GetFeet() + lookAheadRange * aheadRay; stepAhead.z += HumanHeight; if (GetSimpleGroundHeightWithFloor(&stepAhead, &ground)) { jumped = DiscontinuityJump(ground, HOSTAGE_ONLY_JUMP_DOWN, HOSTAGE_MUST_JUMP); } } } move = (pos - GetFeet()); move.z = 0; if (!move.IsZero()) { move.NormalizeInPlace(); } switch (m_moveType) { case Stopped: accelRate = 0; break; case Walking: if (IsCrouching()) accelRate = crouchWalkRate; else accelRate = 400; break; case Running: if (IsCrouching()) accelRate = crouchWalkRate; else accelRate = 1000; break; } m_vel.x = move.x * accelRate * deltaT + m_vel.x; m_vel.y = move.y * accelRate * deltaT + m_vel.y; }
void CHostageImprov::__MAKE_VHOOK(OnTouch)(CBaseEntity *other) { const char *classname; Vector2D to; const float pushForce = 20.0f; classname = STRING(other->pev->classname); if (cv_hostage_debug.value != 0.0) { CONSOLE_ECHO("%5.1f: Hostage hit '%s'\n", gpGlobals->time, classname); } m_collisionTimer.Start(); if (FStrEq(classname, "worldspawn")) { const float lookAheadRange = 30.0f; float ground; Vector normal = Vector(0, 0, 1); Vector alongFloor; TraceResult result; bool isStep = false; UTIL_MakeVectors(m_hostage->pev->angles); if (!GetSimpleGroundHeightWithFloor(&GetEyes(), &ground, &normal)) return; if (cv_hostage_debug.value < 0.0) { UTIL_DrawBeamPoints(GetFeet() + normal * 50, GetFeet(), 2, 255, 255, 0); } alongFloor = CrossProduct(normal, gpGlobals->v_right); Vector pos = alongFloor * lookAheadRange; for (float_precision offset = 1.0f; offset <= 18.0f; offset += 3.0f) { Vector vecStart = GetFeet(); vecStart.z += offset; UTIL_TraceLine(vecStart, vecStart + pos, ignore_monsters, dont_ignore_glass, m_hostage->pev->pContainingEntity, &result); if (result.flFraction < 1.0f && result.vecPlaneNormal.z < MaxUnitZSlope) { isStep = true; break; } } if (isStep) { float stepAheadGround = pos.z; Vector stepAheadNormal = Vector(0, 0, stepAheadGround); m_inhibitObstacleAvoidance.Start(0.5); for (float range = 1.0f; range <= 30.5f; range += 5.0f) { Vector stepAhead = GetFeet() + alongFloor * range; stepAhead.z = GetEyes().z; if (GetSimpleGroundHeightWithFloor(&stepAhead, &stepAheadGround, &stepAheadNormal)) { float dz = stepAheadGround - GetFeet().z; if (dz > 0.0f && dz < 18.0f) { m_hostage->pev->origin.z = stepAheadGround + 3.0f; break; } } } } else if (!IsMoving() && !IsUsingLadder()) { bool isSeam = false; const float checkSeamRange = 50.0f; Vector posBehind; posBehind = GetEyes() - alongFloor * checkSeamRange; UTIL_TraceLine(posBehind, posBehind - Vector(0, 0, 9999), ignore_monsters, dont_ignore_glass, m_hostage->pev->pContainingEntity, &result); if (result.flFraction < 1.0f && DotProduct(result.vecPlaneNormal, normal) < 1.0f) { isSeam = true; } else { Vector posAhead = GetEyes() + alongFloor * checkSeamRange; UTIL_TraceLine(posAhead, posAhead - Vector(0, 0, 9999), ignore_monsters, dont_ignore_glass, m_hostage->pev->pContainingEntity, &result); if (result.flFraction < 1.0f && DotProduct(result.vecPlaneNormal, normal) < 1.0f) isSeam = true; } if (isSeam) { if (cv_hostage_debug.value != 0.0) { CONSOLE_ECHO("Hostage stuck on seam.\n"); } const float nudge = 3.0f; m_hostage->pev->origin.z += nudge; } } } else if (FStrEq(classname, "func_breakable")) { other->TakeDamage(m_hostage->pev, m_hostage->pev, 9999.9, DMG_BULLET); } else if (other->IsPlayer() || FClassnameIs(other->pev, "hostage_entity")) { to = (m_hostage->pev->origin - other->pev->origin).Make2D(); to.NormalizeInPlace(); m_vel.x += to.x * pushForce; m_vel.y += to.y * pushForce; } }
// Move along the path. Return false if end of path reached. CCSBot::PathResult CCSBot::UpdatePathMovement(bool allowSpeedChange) { if (m_pathLength == 0) return PATH_FAILURE; if (cv_bot_walk.value != 0.0f) Walk(); // If we are navigating a ladder, it overrides all other path movement until complete if (UpdateLadderMovement()) return PROGRESSING; // ladder failure can destroy the path if (m_pathLength == 0) return PATH_FAILURE; // we are not supposed to be on a ladder - if we are, jump off if (IsOnLadder()) Jump(MUST_JUMP); assert(m_pathIndex < m_pathLength); // Check if reached the end of the path bool nearEndOfPath = false; if (m_pathIndex >= m_pathLength - 1) { Vector toEnd(pev->origin.x, pev->origin.y, GetFeetZ()); Vector d = GetPathEndpoint() - toEnd; // can't use 2D because path end may be below us (jump down) const float walkRange = 200.0f; // walk as we get close to the goal position to ensure we hit it if (d.IsLengthLessThan(walkRange)) { // don't walk if crouching - too slow if (allowSpeedChange && !IsCrouching()) Walk(); // note if we are near the end of the path const float nearEndRange = 50.0f; if (d.IsLengthLessThan(nearEndRange)) nearEndOfPath = true; const float closeEpsilon = 20.0f; if (d.IsLengthLessThan(closeEpsilon)) { // reached goal position - path complete DestroyPath(); // TODO: We should push and pop walk state here, in case we want to continue walking after reaching goal if (allowSpeedChange) Run(); return END_OF_PATH; } } } // To keep us moving smoothly, we will move towards // a point farther ahead of us down our path. int prevIndex = 0; // closest index on path just prior to where we are now const float aheadRange = 300.0f; int newIndex = FindPathPoint(aheadRange, &m_goalPosition, &prevIndex); // BOTPORT: Why is prevIndex sometimes -1? if (prevIndex < 0) prevIndex = 0; // if goal position is near to us, we must be about to go around a corner - so look ahead! const float nearCornerRange = 100.0f; if (m_pathIndex < m_pathLength - 1 && (m_goalPosition - pev->origin).IsLengthLessThan(nearCornerRange)) { ClearLookAt(); InhibitLookAround(0.5f); } // if we moved to a new node on the path, setup movement if (newIndex > m_pathIndex) { SetPathIndex(newIndex); } if (!IsUsingLadder()) { // Crouching // if we are approaching a crouch area, crouch // if there are no crouch areas coming up, stand const float crouchRange = 50.0f; bool didCrouch = false; for (int i = prevIndex; i < m_pathLength; i++) { const CNavArea *to = m_path[i].area; // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump // unless we are already higher than the jump area - we must've jumped already but not moved into next area if ((to->GetAttributes() & NAV_JUMP)/* && to->GetCenter()->z > GetFeetZ()*/) break; Vector close; to->GetClosestPointOnArea(&pev->origin, &close); if ((close - pev->origin).Make2D().IsLengthGreaterThan(crouchRange)) break; if (to->GetAttributes() & NAV_CROUCH) { Crouch(); didCrouch = true; break; } } if (!didCrouch && !IsJumping()) { // no crouch areas coming up StandUp(); } // end crouching logic } // compute our forward facing angle m_forwardAngle = UTIL_VecToYaw(m_goalPosition - pev->origin); // Look farther down the path to "lead" our view around corners Vector toGoal; if (m_pathIndex == 0) { toGoal = m_path[1].pos; } else if (m_pathIndex < m_pathLength) { toGoal = m_path[m_pathIndex].pos - pev->origin; // actually aim our view farther down the path const float lookAheadRange = 500.0f; if (!m_path[m_pathIndex].ladder && !IsNearJump() && toGoal.Make2D().IsLengthLessThan(lookAheadRange)) { float along = toGoal.Length2D(); int i; for (i = m_pathIndex + 1; i < m_pathLength; i++) { Vector delta = m_path[i].pos - m_path[i - 1].pos; float segmentLength = delta.Length2D(); if (along + segmentLength >= lookAheadRange) { // interpolate between points to keep look ahead point at fixed distance float t = (lookAheadRange - along) / (segmentLength + along); Vector target; if (t <= 0.0f) target = m_path[i - 1].pos; else if (t >= 1.0f) target = m_path[i].pos; else target = m_path[i - 1].pos + t * delta; toGoal = target - pev->origin; break; } // if we are coming up to a ladder or a jump, look at it if (m_path[i].ladder || (m_path[i].area->GetAttributes() & NAV_JUMP)) { toGoal = m_path[i].pos - pev->origin; break; } along += segmentLength; } if (i == m_pathLength) { toGoal = GetPathEndpoint() - pev->origin; } } } else { toGoal = GetPathEndpoint() - pev->origin; } m_lookAheadAngle = UTIL_VecToYaw(toGoal); // initialize "adjusted" goal to current goal Vector adjustedGoal = m_goalPosition; // Use short "feelers" to veer away from close-range obstacles // Feelers come from our ankles, just above StepHeight, so we avoid short walls, too // Don't use feelers if very near the end of the path, or about to jump // TODO: Consider having feelers at several heights to deal with overhangs, etc. if (!nearEndOfPath && !IsNearJump() && !IsJumping()) { FeelerReflexAdjustment(&adjustedGoal); } // draw debug visualization if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f) { DrawPath(); const Vector *pos = &m_path[m_pathIndex].pos; UTIL_DrawBeamPoints(*pos, *pos + Vector(0, 0, 50), 1, 255, 255, 0); UTIL_DrawBeamPoints(adjustedGoal, adjustedGoal + Vector(0, 0, 50), 1, 255, 0, 255); UTIL_DrawBeamPoints(pev->origin, adjustedGoal + Vector(0, 0, 50), 1, 255, 0, 255); } // dont use adjustedGoal, as it can vary wildly from the feeler adjustment if (!IsAttacking() && IsFriendInTheWay(&m_goalPosition)) { if (!m_isWaitingBehindFriend) { m_isWaitingBehindFriend = true; const float politeDuration = 5.0f - 3.0f * GetProfile()->GetAggression(); m_politeTimer.Start(politeDuration); } else if (m_politeTimer.IsElapsed()) { // we have run out of patience m_isWaitingBehindFriend = false; ResetStuckMonitor(); // repath to avoid clump of friends in the way DestroyPath(); } } else if (m_isWaitingBehindFriend) { // we're done waiting for our friend to move m_isWaitingBehindFriend = false; ResetStuckMonitor(); } // Move along our path if there are no friends blocking our way, // or we have run out of patience if (!m_isWaitingBehindFriend || m_politeTimer.IsElapsed()) { // Move along path MoveTowardsPosition(&adjustedGoal); // Stuck check if (m_isStuck && !IsJumping()) { Wiggle(); } } // if our goal is high above us, we must have fallen bool didFall = false; if (m_goalPosition.z - GetFeetZ() > JumpCrouchHeight) { const float closeRange = 75.0f; Vector2D to(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y); if (to.IsLengthLessThan(closeRange)) { // we can't reach the goal position // check if we can reach the next node, in case this was a "jump down" situation if (m_pathIndex < m_pathLength - 1) { if (m_path[m_pathIndex + 1].pos.z - GetFeetZ() > JumpCrouchHeight) { // the next node is too high, too - we really did fall of the path didFall = true; } } else { // fell trying to get to the last node in the path didFall = true; } } } // This timeout check is needed if the bot somehow slips way off // of its path and cannot progress, but also moves around // enough that it never becomes "stuck" const float giveUpDuration = 5.0f; // 4.0f if (didFall || gpGlobals->time - m_areaEnteredTimestamp > giveUpDuration) { if (didFall) { PrintIfWatched("I fell off!\n"); } // if we havent made any progress in a long time, give up if (m_pathIndex < m_pathLength - 1) { PrintIfWatched("Giving up trying to get to area #%d\n", m_path[m_pathIndex].area->GetID()); } else { PrintIfWatched("Giving up trying to get to end of path\n"); } Run(); StandUp(); DestroyPath(); return PATH_FAILURE; } return PROGRESSING; }