// 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); } } } }
bool CHostageImprov::__MAKE_VHOOK(TraverseLadder)(const CNavLadder *ladder, NavTraverseType how, const Vector *approachPos, const Vector *departPos, float deltaT) { Vector goal; if (how == GO_LADDER_DOWN) { goal = ladder->m_bottom; AddDirectionVector(&goal, ladder->m_dir, 16); if (ladder->m_top.z - HalfHumanHeight > GetFeet().z) { const float atGoalRange = 50.0f; if ((GetFeet() - goal).Make2D().IsLengthLessThan(atGoalRange)) { m_hostage->pev->velocity.z = -100.0f; } } if (ladder->m_bottom.z + HalfHumanHeight > GetFeet().z) return true; } else if (m_traversingLadder) { goal = ladder->m_top; AddDirectionVector(&goal, ladder->m_dir, 16); const float walkRange = 100.0f; if ((GetFeet() - goal).Make2D().IsLengthLessThan(walkRange)) { Walk(); } const float ladderRange = 40.0f; if ((GetFeet() - goal).Make2D().IsLengthLessThan(ladderRange)) { m_hostage->pev->velocity.z = 150.0; } if (GetFeet().z > ladder->m_top.z) m_traversingLadder = false; } else { if (departPos != NULL) { float closeRange = 1e6; float range; if (ladder->m_topForwardArea != NULL) { range = (*departPos - *ladder->m_topForwardArea->GetCenter()).LengthSquared(); if (range < closeRange) { closeRange = range; goal = *ladder->m_topForwardArea->GetCenter(); if (ladder->m_topForwardArea->GetAttributes() & NAV_CROUCH) { Crouch(); } } } if (ladder->m_topLeftArea != NULL) { range = (*departPos - *ladder->m_topLeftArea->GetCenter()).LengthSquared(); if (closeRange > range) { goal = *ladder->m_topLeftArea->GetCenter(); if (ladder->m_topLeftArea->GetAttributes() & NAV_CROUCH) { Crouch(); } } } if (ladder->m_topRightArea != NULL) { range = (*departPos - *ladder->m_topRightArea->GetCenter()).LengthSquared(); if (closeRange > range) { goal = *ladder->m_topRightArea->GetCenter(); if (ladder->m_topRightArea->GetAttributes() & NAV_CROUCH) { Crouch(); } } } } const float ladderRange = 20.0f; if ((GetFeet() - goal).Make2D().IsLengthLessThan(ladderRange)) { return true; } } TrackPath(goal, deltaT); return false; }
/** * 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; }
// Navigate our current ladder. Return true if we are doing ladder navigation. // TODO: Need Push() and Pop() for run/walk context to keep ladder speed contained. bool CCSBot::UpdateLadderMovement() { if (!m_pathLadder) return false; bool giveUp = false; // check for timeout const float ladderTimeoutDuration = 10.0f; if (gpGlobals->time - m_pathLadderTimestamp > ladderTimeoutDuration) { PrintIfWatched("Ladder timeout!\n"); giveUp = true; } else if (m_pathLadderState == APPROACH_ASCENDING_LADDER || m_pathLadderState == APPROACH_DESCENDING_LADDER || m_pathLadderState == ASCEND_LADDER || m_pathLadderState == DESCEND_LADDER || m_pathLadderState == DISMOUNT_ASCENDING_LADDER || m_pathLadderState == MOVE_TO_DESTINATION) { if (m_isStuck) { PrintIfWatched("Giving up ladder - stuck\n"); giveUp = true; } } if (giveUp) { // jump off ladder and give up Jump(MUST_JUMP); Wiggle(); ResetStuckMonitor(); DestroyPath(); Run(); return false; } ResetStuckMonitor(); // check if somehow we totally missed the ladder switch (m_pathLadderState) { case MOUNT_ASCENDING_LADDER: case MOUNT_DESCENDING_LADDER: case ASCEND_LADDER: case DESCEND_LADDER: { const float farAway = 200.0f; Vector2D d = (m_pathLadder->m_top - pev->origin).Make2D(); if (d.IsLengthGreaterThan(farAway)) { PrintIfWatched("Missed ladder\n"); Jump(MUST_JUMP); DestroyPath(); Run(); return false; } break; } } m_areaEnteredTimestamp = gpGlobals->time; const float tolerance = 10.0f; const float closeToGoal = 25.0f; switch (m_pathLadderState) { case APPROACH_ASCENDING_LADDER: { bool approached = false; Vector2D d(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y); if (d.x * m_pathLadder->m_dirVector.x + d.y * m_pathLadder->m_dirVector.y < 0.0f) { Vector2D perp(-m_pathLadder->m_dirVector.y, m_pathLadder->m_dirVector.x); #ifdef REGAMEDLL_FIXES if (Q_abs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal) #else if (Q_abs(int64(d.x * perp.x + d.y * perp.y)) < tolerance && d.Length() < closeToGoal) #endif approached = true; } // small radius will just slow them down a little for more accuracy in hitting their spot const float walkRange = 50.0f; if (d.IsLengthLessThan(walkRange)) { Walk(); StandUp(); } // TODO: Check that we are on the ladder we think we are if (IsOnLadder()) { m_pathLadderState = ASCEND_LADDER; PrintIfWatched("ASCEND_LADDER\n"); // find actual top in case m_pathLadder penetrates the ceiling ComputeLadderEndpoint(true); } else if (approached) { // face the m_pathLadder m_pathLadderState = FACE_ASCENDING_LADDER; PrintIfWatched("FACE_ASCENDING_LADDER\n"); } else { // move toward ladder mount point MoveTowardsPosition(&m_goalPosition); } break; } case APPROACH_DESCENDING_LADDER: { // fall check if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight) { PrintIfWatched("Fell from ladder.\n"); m_pathLadderState = MOVE_TO_DESTINATION; m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition); AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth); PrintIfWatched("MOVE_TO_DESTINATION\n"); } else { bool approached = false; Vector2D d(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y); if (d.x * m_pathLadder->m_dirVector.x + d.y * m_pathLadder->m_dirVector.y > 0.0f) { Vector2D perp(-m_pathLadder->m_dirVector.y, m_pathLadder->m_dirVector.x); if (Q_abs(int64(d.x * perp.x + d.y * perp.y)) < tolerance && d.Length() < closeToGoal) approached = true; } // if approaching ladder from the side or "ahead", walk if (m_pathLadder->m_topBehindArea != m_lastKnownArea) { const float walkRange = 150.0f; if (!IsCrouching() && d.IsLengthLessThan(walkRange)) Walk(); } // TODO: Check that we are on the ladder we think we are if (IsOnLadder()) { // we slipped onto the ladder - climb it m_pathLadderState = DESCEND_LADDER; Run(); PrintIfWatched("DESCEND_LADDER\n"); // find actual bottom in case m_pathLadder penetrates the floor ComputeLadderEndpoint(false); } else if (approached) { // face the ladder m_pathLadderState = FACE_DESCENDING_LADDER; PrintIfWatched("FACE_DESCENDING_LADDER\n"); } else { // move toward ladder mount point MoveTowardsPosition(&m_goalPosition); } } break; } case FACE_ASCENDING_LADDER: { // find yaw to directly aim at ladder Vector to = m_pathLadder->m_bottom - pev->origin; Vector idealAngle = UTIL_VecToAngles(to); const float angleTolerance = 5.0f; if (AnglesAreEqual(pev->v_angle.y, idealAngle.y, angleTolerance)) { // move toward ladder until we become "on" it Run(); ResetStuckMonitor(); m_pathLadderState = MOUNT_ASCENDING_LADDER; PrintIfWatched("MOUNT_ASCENDING_LADDER\n"); } break; } case FACE_DESCENDING_LADDER: { // find yaw to directly aim at ladder Vector to = m_pathLadder->m_top - pev->origin; Vector idealAngle = UTIL_VecToAngles(to); const float angleTolerance = 5.0f; if (AnglesAreEqual(pev->v_angle.y, idealAngle.y, angleTolerance)) { // move toward ladder until we become "on" it m_pathLadderState = MOUNT_DESCENDING_LADDER; ResetStuckMonitor(); PrintIfWatched("MOUNT_DESCENDING_LADDER\n"); } break; } case MOUNT_ASCENDING_LADDER: { if (IsOnLadder()) { m_pathLadderState = ASCEND_LADDER; PrintIfWatched("ASCEND_LADDER\n"); // find actual top in case m_pathLadder penetrates the ceiling ComputeLadderEndpoint(true); } MoveForward(); break; } case MOUNT_DESCENDING_LADDER: { // fall check if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight) { PrintIfWatched("Fell from ladder.\n"); m_pathLadderState = MOVE_TO_DESTINATION; m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition); AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth); PrintIfWatched("MOVE_TO_DESTINATION\n"); } else { if (IsOnLadder()) { m_pathLadderState = DESCEND_LADDER; PrintIfWatched("DESCEND_LADDER\n"); // find actual bottom in case m_pathLadder penetrates the floor ComputeLadderEndpoint(false); } // move toward ladder mount point MoveForward(); } break; } case ASCEND_LADDER: { // run, so we can make our dismount jump to the side, if necessary Run(); // if our destination area requires us to crouch, do it if (m_path[m_pathIndex].area->GetAttributes() & NAV_CROUCH) Crouch(); // did we reach the top? if (GetFeetZ() >= m_pathLadderEnd) { // we reached the top - dismount m_pathLadderState = DISMOUNT_ASCENDING_LADDER; PrintIfWatched("DISMOUNT_ASCENDING_LADDER\n"); if (m_path[m_pathIndex].area == m_pathLadder->m_topForwardArea) m_pathLadderDismountDir = FORWARD; else if (m_path[m_pathIndex].area == m_pathLadder->m_topLeftArea) m_pathLadderDismountDir = LEFT; else if (m_path[m_pathIndex].area == m_pathLadder->m_topRightArea) m_pathLadderDismountDir = RIGHT; m_pathLadderDismountTimestamp = gpGlobals->time; } else if (!IsOnLadder()) { // we fall off the ladder, repath DestroyPath(); return false; } // move up ladder MoveForward(); break; } case DESCEND_LADDER: { Run(); float destHeight = m_pathLadderEnd + HalfHumanHeight; if (!IsOnLadder() || GetFeetZ() <= destHeight) { // we reached the bottom, or we fell off - dismount m_pathLadderState = MOVE_TO_DESTINATION; m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition); AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth); PrintIfWatched("MOVE_TO_DESTINATION\n"); } // Move down ladder MoveForward(); break; } case DISMOUNT_ASCENDING_LADDER: { if (gpGlobals->time - m_pathLadderDismountTimestamp >= 0.4f) { m_pathLadderState = MOVE_TO_DESTINATION; m_path[m_pathIndex].area->GetClosestPointOnArea(&pev->origin, &m_goalPosition); PrintIfWatched("MOVE_TO_DESTINATION\n"); } // We should already be facing the dismount point if (m_pathLadderFaceIn) { switch (m_pathLadderDismountDir) { case LEFT: StrafeLeft(); break; case RIGHT: StrafeRight(); break; case FORWARD: MoveForward(); break; } } else { switch (m_pathLadderDismountDir) { case LEFT: StrafeRight(); break; case RIGHT: StrafeLeft(); break; case FORWARD: MoveBackward(); break; } } break; } case MOVE_TO_DESTINATION: { if (m_path[m_pathIndex].area->Contains(&pev->origin)) { // successfully traversed ladder and reached destination area // exit ladder state machine PrintIfWatched("Ladder traversed.\n"); m_pathLadder = nullptr; // incrememnt path index to next step beyond this ladder SetPathIndex(m_pathIndex + 1); return false; } MoveTowardsPosition(&m_goalPosition); break; } } return true; }