// If next step of path uses a ladder, prepare to traverse it void CCSBot::SetupLadderMovement() { if (m_pathIndex < 1 || m_pathLength == 0) return; const ConnectInfo *to = &m_path[m_pathIndex]; if (to->ladder) { m_spotEncounter = nullptr; m_areaEnteredTimestamp = gpGlobals->time; m_pathLadder = to->ladder; m_pathLadderTimestamp = gpGlobals->time; // to get to next area, we must traverse a ladder if (to->how == GO_LADDER_UP) { m_pathLadderState = APPROACH_ASCENDING_LADDER; m_pathLadderFaceIn = true; PrintIfWatched("APPROACH_ASCENDING_LADDER\n"); m_goalPosition = m_pathLadder->m_bottom; AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth * 2.0f); m_lookAheadAngle = DirectionToAngle(OppositeDirection(m_pathLadder->m_dir)); } else { // try to mount ladder "face out" first m_goalPosition = m_pathLadder->m_top; AddDirectionVector(&m_goalPosition, OppositeDirection(m_pathLadder->m_dir), HalfHumanWidth * 2.0f); TraceResult result; Vector from = m_pathLadder->m_top; Vector to = m_goalPosition; UTIL_TraceLine(from, to, ignore_monsters, ENT(m_pathLadder->m_entity->pev), &result); if (result.flFraction == 1.0f) { PrintIfWatched("APPROACH_DESCENDING_LADDER (face out)\n"); m_pathLadderState = APPROACH_DESCENDING_LADDER; m_pathLadderFaceIn = false; m_lookAheadAngle = DirectionToAngle(m_pathLadder->m_dir); } else { PrintIfWatched("APPROACH_DESCENDING_LADDER (face in)\n"); m_pathLadderState = APPROACH_DESCENDING_LADDER; m_pathLadderFaceIn = true; m_lookAheadAngle = DirectionToAngle(OppositeDirection(m_pathLadder->m_dir)); m_goalPosition = m_pathLadder->m_top; AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth); } } } }
/** * Connect this ladder to given area */ void CNavLadder::ConnectTo( CNavArea *area ) { float center = (m_top.z + m_bottom.z) * 0.5f; if (area->GetCenter().z > center) { // connect to top NavDirType dir; Vector dirVector = area->GetCenter() - m_top; if ( fabs( dirVector.x ) > fabs( dirVector.y ) ) { if ( dirVector.x > 0.0f ) // east { dir = EAST; } else // west { dir = WEST; } } else { if ( dirVector.y > 0.0f ) // south { dir = SOUTH; } else // north { dir = NORTH; } } if ( m_dir == dir ) { m_topBehindArea = area; } else if ( OppositeDirection( m_dir ) == dir ) { m_topForwardArea = area; } else if ( DirectionLeft( m_dir ) == dir ) { m_topLeftArea = area; } else { m_topRightArea = area; } } else { // connect to bottom m_bottomArea = area; } }
bool Map::hasFace(MapCoordinates Coordinates, Direction DirectionType) const { MapCoordinates TargetMapCoordinates = Coordinates; Direction TargetFace = DirectionType; if (isDirectionPositive(DirectionType)) // East, South and Top Directions get translated to adjacent Cells avoiding a bounce back call { // Do something for edge of Map cases?? TargetMapCoordinates.TranslateMapCoordinates(DirectionType); TargetFace = OppositeDirection(TargetFace); } CellCoordinates TargetCellCoordinates = CellCoordinates(TargetMapCoordinates); Cell* TargetCell = getCell(TargetCellCoordinates); if (TargetCell != NULL) { return TargetCell->hasFace(CubeCoordinates(TargetMapCoordinates), TargetFace); } return false; }
// 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; }
/** * Determine actual path positions */ bool CNavPath::ComputePathPositions( void ) { if (m_segmentCount == 0) return false; // start in first area's center m_path[0].pos = *m_path[0].area->GetCenter(); m_path[0].ladder = NULL; m_path[0].how = NUM_TRAVERSE_TYPES; for( int i=1; i<m_segmentCount; ++i ) { const PathSegment *from = &m_path[ i-1 ]; PathSegment *to = &m_path[ i ]; if (to->how <= GO_WEST) // walk along the floor to the next area { to->ladder = NULL; // compute next point, keeping path as straight as possible from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, &from->pos, &to->pos ); // move goal position into the goal area a bit const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist ); // we need to walk out of "from" area, so keep Z where we can reach it to->pos.z = from->area->GetZ( &to->pos ); // if this is a "jump down" connection, we must insert an additional point on the path if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false) { // this is a "jump down" link // compute direction of path just prior to "jump down" Vector2D dir; DirectionToVector2D( (NavDirType)to->how, &dir ); // shift top of "jump down" out a bit to "get over the ledge" const float pushDist = 25.0f; to->pos.x += pushDist * dir.x; to->pos.y += pushDist * dir.y; // insert a duplicate node to represent the bottom of the fall if (m_segmentCount < MAX_PATH_SEGMENTS-1) { // copy nodes down for( int j=m_segmentCount; j>i; --j ) m_path[j] = m_path[j-1]; // path is one node longer ++m_segmentCount; // move index ahead into the new node we just duplicated ++i; m_path[i].pos.x = to->pos.x + pushDist * dir.x; m_path[i].pos.y = to->pos.y + pushDist * dir.y; // put this one at the bottom of the fall m_path[i].pos.z = to->area->GetZ( &m_path[i].pos ); } } } else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder { // find our ladder const NavLadderList *list = from->area->GetLadderList( LADDER_UP ); NavLadderList::const_iterator iter; for( iter = list->begin(); iter != list->end(); ++iter ) { CNavLadder *ladder = *iter; // can't use "behind" area when ascending... if (ladder->m_topForwardArea == to->area || ladder->m_topLeftArea == to->area || ladder->m_topRightArea == to->area) { to->ladder = ladder; to->pos = ladder->m_bottom; AddDirectionVector( &to->pos, ladder->m_dir, 2.0f*HalfHumanWidth ); break; } } if (iter == list->end()) { //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); return false; } } else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder { // find our ladder const NavLadderList *list = from->area->GetLadderList( LADDER_DOWN ); NavLadderList::const_iterator iter; for( iter = list->begin(); iter != list->end(); ++iter ) { CNavLadder *ladder = *iter; if (ladder->m_bottomArea == to->area) { to->ladder = ladder; to->pos = ladder->m_top; AddDirectionVector( &to->pos, OppositeDirection( ladder->m_dir ), 2.0f*HalfHumanWidth ); break; } } if (iter == list->end()) { //PrintIfWatched( "ERROR: Can't find ladder in path\n" ); return false; } } } return true; }