You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1757 lines
44 KiB
1757 lines
44 KiB
8 years ago
|
#include "bot_common.h"
|
||
|
|
||
|
// Determine actual path positions bot will move between along the path
|
||
|
|
||
|
bool CCSBot::ComputePathPositions()
|
||
|
{
|
||
|
if (m_pathLength == 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_pathLength; ++i)
|
||
|
{
|
||
|
const ConnectInfo *from = &m_path[i - 1];
|
||
|
ConnectInfo *to = &m_path[ i ];
|
||
|
|
||
|
// walk along the floor to the next area
|
||
|
if (to->how <= GO_WEST)
|
||
|
{
|
||
|
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
|
||
|
// how far to "step into" an area - must be less than min area size
|
||
|
const float stepInDist = 5.0f;
|
||
|
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; // 75.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_pathLength < MAX_PATH_LENGTH - 1)
|
||
|
{
|
||
|
// copy nodes down
|
||
|
for (int j = m_pathLength; j > i; --j)
|
||
|
m_path[j] = m_path[j - 1];
|
||
|
|
||
|
// path is one node longer
|
||
|
++m_pathLength;
|
||
|
|
||
|
// 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);
|
||
|
int it;
|
||
|
for (it = list->Head (); it != list->InvalidIndex (); it = list->Next (it))
|
||
|
{
|
||
|
CNavLadder *ladder = (*list)[it];
|
||
|
|
||
|
// 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 (it == list->InvalidIndex ())
|
||
|
{
|
||
|
//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);
|
||
|
int it;
|
||
|
for (it = list->Head (); it != list->InvalidIndex (); it = list->Next (it))
|
||
|
{
|
||
|
CNavLadder *ladder = (*list)[it];
|
||
|
|
||
|
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 (it == list->InvalidIndex ())
|
||
|
{
|
||
|
//PrintIfWatched( "ERROR: Can't find ladder in path\n" );
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// 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 != NULL)
|
||
|
{
|
||
|
m_spotEncounter = NULL;
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO: What about ladders whose top AND bottom are messed up?
|
||
|
|
||
|
void CCSBot::ComputeLadderEndpoint(bool isAscending)
|
||
|
{
|
||
|
TraceResult result;
|
||
|
Vector from, to;
|
||
|
|
||
|
if (isAscending)
|
||
|
{
|
||
|
// find actual top in case m_pathLadder penetrates the ceiling
|
||
|
// trace from our chest height at m_pathLadder base
|
||
|
from = m_pathLadder->m_bottom;
|
||
|
from.z = pev->origin.z;
|
||
|
to = m_pathLadder->m_top;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// find actual bottom in case m_pathLadder penetrates the floor
|
||
|
// trace from our chest height at m_pathLadder top
|
||
|
from = m_pathLadder->m_top;
|
||
|
from.z = pev->origin.z;
|
||
|
to = m_pathLadder->m_bottom;
|
||
|
}
|
||
|
|
||
|
UTIL_TraceLine(from, to, ignore_monsters, ENT(m_pathLadder->m_entity->pev), &result);
|
||
|
|
||
|
if (result.flFraction == 1.0f)
|
||
|
m_pathLadderEnd = to.z;
|
||
|
else
|
||
|
m_pathLadderEnd = from.z + result.flFraction * (to.z - from.z);
|
||
|
}
|
||
|
|
||
|
// 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 == NULL)
|
||
|
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);
|
||
|
|
||
|
if (abs(int64(d.x * perp.x + d.y * perp.y)) < tolerance && d.Length() < closeToGoal)
|
||
|
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 (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 = NULL;
|
||
|
|
||
|
// incrememnt path index to next step beyond this ladder
|
||
|
SetPathIndex(m_pathIndex + 1);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
MoveTowardsPosition(&m_goalPosition);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Compute closest point on path to given point
|
||
|
// NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
|
||
|
|
||
|
bool CCSBot::FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const
|
||
|
{
|
||
|
if (!HasPath() || close == NULL)
|
||
|
return false;
|
||
|
|
||
|
Vector along, toWorldPos;
|
||
|
Vector pos;
|
||
|
const Vector *from, *to;
|
||
|
float length;
|
||
|
float closeLength;
|
||
|
float closeDistSq = 9999999999.9f;
|
||
|
float distSq;
|
||
|
|
||
|
for (int i = startIndex; i <= endIndex; ++i)
|
||
|
{
|
||
|
from = &m_path[i - 1].pos;
|
||
|
to = &m_path[i].pos;
|
||
|
|
||
|
// compute ray along this path segment
|
||
|
along = *to - *from;
|
||
|
|
||
|
// make it a unit vector along the path
|
||
|
length = along.NormalizeInPlace();
|
||
|
|
||
|
// compute vector from start of segment to our point
|
||
|
toWorldPos = *worldPos - *from;
|
||
|
|
||
|
// find distance of closest point on ray
|
||
|
closeLength = DotProduct(toWorldPos, along);
|
||
|
|
||
|
// constrain point to be on path segment
|
||
|
if (closeLength <= 0.0f)
|
||
|
pos = *from;
|
||
|
else if (closeLength >= length)
|
||
|
pos = *to;
|
||
|
else
|
||
|
pos = *from + closeLength * along;
|
||
|
|
||
|
distSq = (pos - *worldPos).LengthSquared();
|
||
|
|
||
|
// keep the closest point so far
|
||
|
if (distSq < closeDistSq)
|
||
|
{
|
||
|
closeDistSq = distSq;
|
||
|
*close = pos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Return the closest point to our current position on our current path
|
||
|
// If "local" is true, only check the portion of the path surrounding m_pathIndex.
|
||
|
|
||
|
int CCSBot::FindOurPositionOnPath(Vector *close, bool local) const
|
||
|
{
|
||
|
if (!HasPath())
|
||
|
return -1;
|
||
|
|
||
|
Vector along, toFeet;
|
||
|
Vector feet = Vector(pev->origin.x, pev->origin.y, GetFeetZ());
|
||
|
Vector eyes = feet + Vector(0, 0, HalfHumanHeight); // in case we're crouching
|
||
|
Vector pos;
|
||
|
const Vector *from, *to;
|
||
|
float length;
|
||
|
float closeLength;
|
||
|
float closeDistSq = 9999999999.9;
|
||
|
int closeIndex = -1;
|
||
|
float distSq;
|
||
|
|
||
|
int start, end;
|
||
|
|
||
|
if (local)
|
||
|
{
|
||
|
start = m_pathIndex - 3;
|
||
|
if (start < 1)
|
||
|
start = 1;
|
||
|
|
||
|
end = m_pathIndex + 3;
|
||
|
if (end > m_pathLength)
|
||
|
end = m_pathLength;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
start = 1;
|
||
|
end = m_pathLength;
|
||
|
}
|
||
|
|
||
|
for (int i = start; i < end; ++i)
|
||
|
{
|
||
|
from = &m_path[i - 1].pos;
|
||
|
to = &m_path[i].pos;
|
||
|
|
||
|
// compute ray along this path segment
|
||
|
along = *to - *from;
|
||
|
|
||
|
// make it a unit vector along the path
|
||
|
length = along.NormalizeInPlace();
|
||
|
|
||
|
// compute vector from start of segment to our point
|
||
|
toFeet = feet - *from;
|
||
|
|
||
|
// find distance of closest point on ray
|
||
|
closeLength = DotProduct(toFeet, along);
|
||
|
|
||
|
// constrain point to be on path segment
|
||
|
if (closeLength <= 0.0f)
|
||
|
pos = *from;
|
||
|
else if (closeLength >= length)
|
||
|
pos = *to;
|
||
|
else
|
||
|
pos = *from + closeLength * along;
|
||
|
|
||
|
distSq = (pos - feet).LengthSquared();
|
||
|
|
||
|
// keep the closest point so far
|
||
|
if (distSq < closeDistSq)
|
||
|
{
|
||
|
// don't use points we cant see
|
||
|
Vector probe = pos + Vector(0, 0, HalfHumanHeight);
|
||
|
if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_EVERYTHING))
|
||
|
continue;
|
||
|
|
||
|
// don't use points we cant reach
|
||
|
if (!IsStraightLinePathWalkable(&pos))
|
||
|
continue;
|
||
|
|
||
|
closeDistSq = distSq;
|
||
|
if (close)
|
||
|
*close = pos;
|
||
|
|
||
|
closeIndex = i - 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return closeIndex;
|
||
|
}
|
||
|
|
||
|
// Test for un-jumpable height change, or unrecoverable fall
|
||
|
|
||
|
bool CCSBot::IsStraightLinePathWalkable(const Vector *goal) const
|
||
|
{
|
||
|
// this is causing hang-up problems when crawling thru ducts/windows that drop off into rooms (they fail the "falling" check)
|
||
|
return true;
|
||
|
|
||
|
const float inc = GenerationStepSize;
|
||
|
|
||
|
Vector feet = pev->origin;
|
||
|
Vector dir = *goal - feet;
|
||
|
float length = dir.NormalizeInPlace();
|
||
|
|
||
|
float lastGround;
|
||
|
|
||
|
//if (!GetSimpleGroundHeight(&pev->origin, &lastGround))
|
||
|
// return false;
|
||
|
|
||
|
lastGround = feet.z;
|
||
|
|
||
|
float along = 0.0f;
|
||
|
Vector pos;
|
||
|
float ground;
|
||
|
bool done = false;
|
||
|
|
||
|
while (!done)
|
||
|
{
|
||
|
along += inc;
|
||
|
if (along > length)
|
||
|
{
|
||
|
along = length;
|
||
|
done = true;
|
||
|
}
|
||
|
|
||
|
// compute step along path
|
||
|
pos = feet + along * dir;
|
||
|
pos.z += HalfHumanHeight;
|
||
|
|
||
|
if (!GetSimpleGroundHeight(&pos, &ground))
|
||
|
return false;
|
||
|
|
||
|
// check for falling
|
||
|
if (ground - lastGround < -StepHeight)
|
||
|
return false;
|
||
|
|
||
|
// check for unreachable jump
|
||
|
// use slightly shorter jump limit, to allow for some fudge room
|
||
|
if (ground - lastGround > JumpHeight)
|
||
|
return false;
|
||
|
|
||
|
lastGround = ground;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Compute a point a fixed distance ahead along our path.
|
||
|
// Returns path index just after point.
|
||
|
|
||
|
int CCSBot::FindPathPoint(float aheadRange, Vector *point, int *prevIndex)
|
||
|
{
|
||
|
// find path index just past aheadRange
|
||
|
int afterIndex;
|
||
|
|
||
|
// finds the closest point on local area of path, and returns the path index just prior to it
|
||
|
Vector close;
|
||
|
int startIndex = FindOurPositionOnPath(&close, true);
|
||
|
|
||
|
if (prevIndex)
|
||
|
*prevIndex = startIndex;
|
||
|
|
||
|
if (startIndex <= 0)
|
||
|
{
|
||
|
// went off the end of the path
|
||
|
// or next point in path is unwalkable (ie: jump-down)
|
||
|
// keep same point
|
||
|
return m_pathIndex;
|
||
|
}
|
||
|
|
||
|
// if we are crouching, just follow the path exactly
|
||
|
if (IsCrouching())
|
||
|
{
|
||
|
// we want to move to the immediately next point along the path from where we are now
|
||
|
int index = startIndex + 1;
|
||
|
if (index >= m_pathLength)
|
||
|
index = m_pathLength - 1;
|
||
|
|
||
|
*point = m_path[ index ].pos;
|
||
|
|
||
|
// if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
|
||
|
// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
|
||
|
const float closeEpsilon = 20.0f; // 10.0f
|
||
|
while ((*point - close).Make2D().IsLengthLessThan(closeEpsilon))
|
||
|
{
|
||
|
++index;
|
||
|
|
||
|
if (index >= m_pathLength)
|
||
|
{
|
||
|
index = m_pathLength - 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
*point = m_path[ index ].pos;
|
||
|
}
|
||
|
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
// make sure we use a node a minimum distance ahead of us, to avoid wiggling
|
||
|
while (startIndex < m_pathLength - 1)
|
||
|
{
|
||
|
Vector pos = m_path[ startIndex + 1 ].pos;
|
||
|
|
||
|
// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
|
||
|
const float closeEpsilon = 20.0f;
|
||
|
if ((pos - close).Make2D().IsLengthLessThan(closeEpsilon))
|
||
|
{
|
||
|
++startIndex;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we hit a ladder, stop, or jump area, must stop (dont use ladder behind us)
|
||
|
if (startIndex > m_pathIndex && startIndex < m_pathLength
|
||
|
&& (m_path[ startIndex ].ladder != NULL || (m_path[ startIndex ].area->GetAttributes() & NAV_JUMP)))
|
||
|
{
|
||
|
*point = m_path[ startIndex ].pos;
|
||
|
return startIndex;
|
||
|
}
|
||
|
|
||
|
// we need the point just *ahead* of us
|
||
|
++startIndex;
|
||
|
if (startIndex >= m_pathLength)
|
||
|
startIndex = m_pathLength - 1;
|
||
|
|
||
|
// if we hit a ladder, stop, or jump area, must stop
|
||
|
if (startIndex < m_pathLength && (m_path[ startIndex ].ladder != NULL || (m_path[ startIndex ].area->GetAttributes() & NAV_JUMP)))
|
||
|
{
|
||
|
*point = m_path[ startIndex ].pos;
|
||
|
return startIndex;
|
||
|
}
|
||
|
|
||
|
// note direction of path segment we are standing on
|
||
|
Vector initDir = m_path[ startIndex ].pos - m_path[ startIndex - 1 ].pos;
|
||
|
initDir.NormalizeInPlace();
|
||
|
|
||
|
Vector feet = Vector(pev->origin.x, pev->origin.y, GetFeetZ());
|
||
|
Vector eyes = feet + Vector(0, 0, HalfHumanHeight);
|
||
|
float rangeSoFar = 0;
|
||
|
|
||
|
// this flag is true if our ahead point is visible
|
||
|
bool visible = true;
|
||
|
Vector prevDir = initDir;
|
||
|
|
||
|
// step along the path until we pass aheadRange
|
||
|
bool isCorner = false;
|
||
|
int i;
|
||
|
for (i = startIndex; i < m_pathLength; ++i)
|
||
|
{
|
||
|
Vector pos = m_path[i].pos;
|
||
|
Vector to = pos - m_path[i - 1].pos;
|
||
|
Vector dir = to;
|
||
|
dir.NormalizeInPlace();
|
||
|
|
||
|
// don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
|
||
|
if (DotProduct(dir, initDir) < 0.0f) // -0.25f
|
||
|
{
|
||
|
--i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
|
||
|
if (DotProduct(dir, prevDir) < 0.5f)
|
||
|
{
|
||
|
isCorner = true;
|
||
|
--i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
prevDir = dir;
|
||
|
|
||
|
// don't use points we cant see
|
||
|
Vector probe = pos + Vector(0, 0, HalfHumanHeight);
|
||
|
if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
|
||
|
{
|
||
|
// presumably, the previous point is visible, so we will interpolate
|
||
|
visible = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// if we encounter a ladder or jump area, we must stop
|
||
|
if (i < m_pathLength && (m_path[ i ].ladder != NULL || (m_path[ i ].area->GetAttributes() & NAV_JUMP)))
|
||
|
break;
|
||
|
|
||
|
// Check straight-line path from our current position to this position
|
||
|
// Test for un-jumpable height change, or unrecoverable fall
|
||
|
if (!IsStraightLinePathWalkable(&pos))
|
||
|
{
|
||
|
--i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Vector along = (i == startIndex) ? (pos - feet) : (pos - m_path[i - 1].pos);
|
||
|
rangeSoFar += along.Length2D();
|
||
|
|
||
|
// stop if we have gone farther than aheadRange
|
||
|
if (rangeSoFar >= aheadRange)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (i < startIndex)
|
||
|
afterIndex = startIndex;
|
||
|
else if (i < m_pathLength)
|
||
|
afterIndex = i;
|
||
|
else
|
||
|
afterIndex = m_pathLength - 1;
|
||
|
|
||
|
// compute point on the path at aheadRange
|
||
|
if (afterIndex == 0)
|
||
|
{
|
||
|
*point = m_path[0].pos;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// interpolate point along path segment
|
||
|
const Vector *afterPoint = &m_path[ afterIndex ].pos;
|
||
|
const Vector *beforePoint = &m_path[ afterIndex - 1 ].pos;
|
||
|
|
||
|
Vector to = *afterPoint - *beforePoint;
|
||
|
float length = to.Length2D();
|
||
|
|
||
|
float t = 1.0f - ((rangeSoFar - aheadRange) / length);
|
||
|
|
||
|
if (t < 0.0f)
|
||
|
t = 0.0f;
|
||
|
else if (t > 1.0f)
|
||
|
t = 1.0f;
|
||
|
|
||
|
*point = *beforePoint + t * to;
|
||
|
|
||
|
// if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
|
||
|
if (!visible)
|
||
|
{
|
||
|
const float sightStepSize = 25.0f;
|
||
|
float dt = sightStepSize / length;
|
||
|
|
||
|
Vector probe = *point + Vector(0, 0, HalfHumanHeight);
|
||
|
while (t > 0.0f && !IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
|
||
|
{
|
||
|
t -= dt;
|
||
|
*point = *beforePoint + t * to;
|
||
|
}
|
||
|
|
||
|
if (t <= 0.0f)
|
||
|
*point = *beforePoint;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
|
||
|
if (!isCorner)
|
||
|
{
|
||
|
const float epsilon = 50.0f;
|
||
|
Vector2D toPoint;
|
||
|
toPoint.x = point->x - pev->origin.x;
|
||
|
toPoint.y = point->y - pev->origin.y;
|
||
|
if (DotProduct(toPoint, initDir.Make2D()) < 0.0f || toPoint.IsLengthLessThan(epsilon))
|
||
|
{
|
||
|
int i;
|
||
|
for (i = startIndex; i < m_pathLength; ++i)
|
||
|
{
|
||
|
toPoint.x = m_path[i].pos.x - pev->origin.x;
|
||
|
toPoint.y = m_path[i].pos.y - pev->origin.y;
|
||
|
if (m_path[i].ladder != NULL || (m_path[i].area->GetAttributes() & NAV_JUMP) || toPoint.IsLengthGreaterThan(epsilon))
|
||
|
{
|
||
|
*point = m_path[i].pos;
|
||
|
startIndex = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (i == m_pathLength)
|
||
|
{
|
||
|
*point = GetPathEndpoint();
|
||
|
startIndex = m_pathLength - 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
|
||
|
return startIndex;
|
||
|
}
|
||
|
|
||
|
// Set the current index along the path
|
||
|
|
||
|
void CCSBot::SetPathIndex(int newIndex)
|
||
|
{
|
||
|
m_pathIndex = Q_min(newIndex, m_pathLength - 1);
|
||
|
m_areaEnteredTimestamp = gpGlobals->time;
|
||
|
|
||
|
if (m_path[ m_pathIndex ].ladder)
|
||
|
{
|
||
|
SetupLadderMovement();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// get our "encounter spots" for this leg of the path
|
||
|
if (m_pathIndex < m_pathLength && m_pathIndex >= 2)
|
||
|
m_spotEncounter = m_path[ m_pathIndex - 1 ].area->GetSpotEncounter(m_path[ m_pathIndex - 2 ].area, m_path[ m_pathIndex ].area);
|
||
|
else
|
||
|
m_spotEncounter = NULL;
|
||
|
|
||
|
m_pathLadder = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return true if nearing a jump in the path
|
||
|
|
||
|
bool CCSBot::IsNearJump() const
|
||
|
{
|
||
|
if (m_pathIndex == 0 || m_pathIndex >= m_pathLength)
|
||
|
return false;
|
||
|
|
||
|
for (int i = m_pathIndex - 1; i < m_pathIndex; ++i)
|
||
|
{
|
||
|
if (m_path[ i ].area->GetAttributes() & NAV_JUMP)
|
||
|
{
|
||
|
float dz = m_path[ i + 1 ].pos.z - m_path[ i ].pos.z;
|
||
|
|
||
|
if (dz > 0.0f)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Return approximately how much damage will will take from the given fall height
|
||
|
|
||
|
float CCSBot::GetApproximateFallDamage(float height) const
|
||
|
{
|
||
|
// empirically discovered height values
|
||
|
const float slope = 0.2178f;
|
||
|
const float intercept = 26.0f;
|
||
|
|
||
|
float damage = slope * height - intercept;
|
||
|
|
||
|
if (damage < 0.0f)
|
||
|
return 0.0f;
|
||
|
|
||
|
return damage;
|
||
|
}
|
||
|
|
||
|
// Return true if a friend is between us and the given position
|
||
|
|
||
|
bool CCSBot::IsFriendInTheWay(const Vector *goalPos) const
|
||
|
{
|
||
|
// do this check less often to ease CPU burden
|
||
|
if (!m_avoidFriendTimer.IsElapsed())
|
||
|
{
|
||
|
return m_isFriendInTheWay;
|
||
|
}
|
||
|
|
||
|
const float avoidFriendInterval = 0.5f;
|
||
|
m_avoidFriendTimer.Start(avoidFriendInterval);
|
||
|
|
||
|
// compute ray along intended path
|
||
|
Vector moveDir = *goalPos - pev->origin;
|
||
|
|
||
|
// make it a unit vector
|
||
|
float length = moveDir.NormalizeInPlace();
|
||
|
|
||
|
m_isFriendInTheWay = false;
|
||
|
|
||
|
// check if any friends are overlapping this linear path
|
||
|
for (int i = 1; i <= gpGlobals->maxClients; ++i)
|
||
|
{
|
||
|
CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex(i));
|
||
|
|
||
|
if (player == NULL)
|
||
|
continue;
|
||
|
|
||
|
if (FNullEnt(player->pev))
|
||
|
continue;
|
||
|
|
||
|
if (!player->IsAlive())
|
||
|
continue;
|
||
|
|
||
|
// if (player->m_iTeam != m_iTeam)
|
||
|
// continue;
|
||
|
|
||
|
if (player == this)
|
||
|
continue;
|
||
|
|
||
|
// compute vector from us to our friend
|
||
|
Vector toFriend = player->pev->origin - pev->origin;
|
||
|
|
||
|
// check if friend is in our "personal space"
|
||
|
const float personalSpace = 100.0f;
|
||
|
if (toFriend.IsLengthGreaterThan(personalSpace))
|
||
|
continue;
|
||
|
|
||
|
// find distance of friend along our movement path
|
||
|
float friendDistAlong = DotProduct(toFriend, moveDir);
|
||
|
|
||
|
// if friend is behind us, ignore him
|
||
|
if (friendDistAlong <= 0.0f)
|
||
|
continue;
|
||
|
|
||
|
// constrain point to be on path segment
|
||
|
Vector pos;
|
||
|
if (friendDistAlong >= length)
|
||
|
pos = *goalPos;
|
||
|
else
|
||
|
pos = pev->origin + friendDistAlong * moveDir;
|
||
|
|
||
|
// check if friend overlaps our intended line of movement
|
||
|
const float friendRadius = 30.0f;
|
||
|
if ((pos - player->pev->origin).IsLengthLessThan(friendRadius))
|
||
|
{
|
||
|
// friend is in our personal space and overlaps our intended line of movement
|
||
|
m_isFriendInTheWay = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return m_isFriendInTheWay;
|
||
|
}
|
||
|
|
||
|
// Do reflex avoidance movements if our "feelers" are touched
|
||
|
|
||
|
void CCSBot::FeelerReflexAdjustment(Vector *goalPosition)
|
||
|
{
|
||
|
// if we are in a "precise" area, do not do feeler adjustments
|
||
|
if (m_lastKnownArea != NULL && (m_lastKnownArea->GetAttributes() & NAV_PRECISE))
|
||
|
return;
|
||
|
|
||
|
Vector dir(BotCOS(m_forwardAngle), BotSIN(m_forwardAngle), 0.0f);
|
||
|
Vector lat(-dir.y, dir.x, 0.0f);
|
||
|
|
||
|
const float feelerOffset = (IsCrouching()) ? 15.0f : 20.0f;
|
||
|
const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
|
||
|
const float feelerLengthWalk = 30.0f;
|
||
|
const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
|
||
|
|
||
|
float feelerLength = (IsRunning()) ? feelerLengthRun : feelerLengthWalk;
|
||
|
|
||
|
feelerLength = (IsCrouching()) ? 20.0f : feelerLength;
|
||
|
|
||
|
// Feelers must follow floor slope
|
||
|
float ground;
|
||
|
Vector normal;
|
||
|
|
||
|
//m_eyePos = EyePosition();
|
||
|
m_eyePos.x = pev->origin.x + pev->view_ofs.x;
|
||
|
m_eyePos.y = pev->origin.y + pev->view_ofs.y;
|
||
|
m_eyePos.z = pev->origin.z + pev->view_ofs.z;
|
||
|
|
||
|
if (GetSimpleGroundHeightWithFloor(&m_eyePos, &ground, &normal) == false)
|
||
|
return;
|
||
|
|
||
|
// get forward vector along floor
|
||
|
dir = CrossProduct(lat, normal);
|
||
|
|
||
|
// correct the sideways vector
|
||
|
lat = CrossProduct(dir, normal);
|
||
|
|
||
|
Vector feet = Vector(pev->origin.x, pev->origin.y, GetFeetZ());
|
||
|
feet.z += feelerHeight;
|
||
|
|
||
|
Vector from = feet + feelerOffset * lat;
|
||
|
Vector to = from + feelerLength * dir;
|
||
|
|
||
|
bool leftClear = IsWalkableTraceLineClear(from, to, WALK_THRU_EVERYTHING);
|
||
|
|
||
|
// avoid ledges, too
|
||
|
// use 'from' so it doesn't interfere with legitimate gap jumping (its at our feet)
|
||
|
// TODO: Rethink this - it causes lots of wiggling when bots jump down from vents, etc
|
||
|
/*
|
||
|
float ground;
|
||
|
if (GetSimpleGroundHeightWithFloor(&from, &ground))
|
||
|
{
|
||
|
if (GetFeetZ() - ground > JumpHeight)
|
||
|
leftClear = false;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f)
|
||
|
{
|
||
|
if (leftClear)
|
||
|
UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
|
||
|
else
|
||
|
UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
|
||
|
}
|
||
|
|
||
|
from = feet - feelerOffset * lat;
|
||
|
to = from + feelerLength * dir;
|
||
|
|
||
|
bool rightClear = IsWalkableTraceLineClear(from, to, WALK_THRU_EVERYTHING);
|
||
|
|
||
|
/*
|
||
|
// avoid ledges, too
|
||
|
if (GetSimpleGroundHeightWithFloor(&from, &ground))
|
||
|
{
|
||
|
if (GetFeetZ() - ground > JumpHeight)
|
||
|
rightClear = false;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f)
|
||
|
{
|
||
|
if (rightClear)
|
||
|
UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
|
||
|
else
|
||
|
UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
|
||
|
}
|
||
|
|
||
|
const float avoidRange = (IsCrouching()) ? 150.0f : 300.0f; // 50.0f : 300.0f
|
||
|
|
||
|
if (!rightClear)
|
||
|
{
|
||
|
if (leftClear)
|
||
|
{
|
||
|
// right hit, left clear - veer left
|
||
|
*goalPosition = *goalPosition + avoidRange * lat;
|
||
|
}
|
||
|
}
|
||
|
else if (!leftClear)
|
||
|
{
|
||
|
// right clear, left hit - veer right
|
||
|
*goalPosition = *goalPosition - avoidRange * lat;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
// Crouching
|
||
|
if (!IsUsingLadder())
|
||
|
{
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
// Build trivial path to goal, assuming we are already in the same area
|
||
|
|
||
|
void CCSBot::BuildTrivialPath(const Vector *goal)
|
||
|
{
|
||
|
m_pathIndex = 1;
|
||
|
m_pathLength = 2;
|
||
|
|
||
|
m_path[0].area = m_lastKnownArea;
|
||
|
m_path[0].pos = pev->origin;
|
||
|
m_path[0].pos.z = m_lastKnownArea->GetZ(&pev->origin);
|
||
|
m_path[0].ladder = NULL;
|
||
|
m_path[0].how = NUM_TRAVERSE_TYPES;
|
||
|
|
||
|
m_path[1].area = m_lastKnownArea;
|
||
|
m_path[1].pos = *goal;
|
||
|
m_path[1].pos.z = m_lastKnownArea->GetZ(goal);
|
||
|
m_path[1].ladder = NULL;
|
||
|
m_path[1].how = NUM_TRAVERSE_TYPES;
|
||
|
|
||
|
m_areaEnteredTimestamp = gpGlobals->time;
|
||
|
m_spotEncounter = NULL;
|
||
|
m_pathLadder = NULL;
|
||
|
|
||
|
m_goalPosition = *goal;
|
||
|
}
|
||
|
|
||
|
// Compute shortest path to goal position via A* algorithm
|
||
|
// If 'goalArea' is NULL, path will get as close as it can.
|
||
|
|
||
|
bool CCSBot::ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route)
|
||
|
{
|
||
|
// Throttle re-pathing
|
||
|
if (!m_repathTimer.IsElapsed())
|
||
|
return false;
|
||
|
|
||
|
// randomize to distribute CPU load
|
||
|
m_repathTimer.Start(RANDOM_FLOAT(0.4f, 0.6f));
|
||
|
|
||
|
DestroyPath();
|
||
|
|
||
|
CNavArea *startArea = m_lastKnownArea;
|
||
|
if (startArea == NULL)
|
||
|
return false;
|
||
|
|
||
|
// note final specific position
|
||
|
Vector pathEndPosition;
|
||
|
|
||
|
if (goal == NULL && goalArea == NULL)
|
||
|
return false;
|
||
|
|
||
|
if (goal == NULL)
|
||
|
pathEndPosition = *goalArea->GetCenter();
|
||
|
else
|
||
|
pathEndPosition = *goal;
|
||
|
|
||
|
// make sure path end position is on the ground
|
||
|
if (goalArea)
|
||
|
pathEndPosition.z = goalArea->GetZ(&pathEndPosition);
|
||
|
else
|
||
|
GetGroundHeight(&pathEndPosition, &pathEndPosition.z);
|
||
|
|
||
|
// if we are already in the goal area, build trivial path
|
||
|
if (startArea == goalArea)
|
||
|
{
|
||
|
BuildTrivialPath(&pathEndPosition);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Compute shortest path to goal
|
||
|
CNavArea *closestArea = NULL;
|
||
|
PathCost pathCost(this, route);
|
||
|
bool pathToGoalExists = NavAreaBuildPath(startArea, goalArea, goal, pathCost, &closestArea);
|
||
|
|
||
|
CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;
|
||
|
|
||
|
// Build path by following parent links
|
||
|
// get count
|
||
|
int count = 0;
|
||
|
CNavArea *area;
|
||
|
for (area = effectiveGoalArea; area != NULL; area = area->GetParent())
|
||
|
{
|
||
|
++count;
|
||
|
}
|
||
|
|
||
|
// save room for endpoint
|
||
|
if (count > MAX_PATH_LENGTH - 1)
|
||
|
count = MAX_PATH_LENGTH - 1;
|
||
|
|
||
|
if (count == 0)
|
||
|
return false;
|
||
|
|
||
|
if (count == 1)
|
||
|
{
|
||
|
BuildTrivialPath(&pathEndPosition);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// build path
|
||
|
m_pathLength = count;
|
||
|
for (area = effectiveGoalArea; count && area != NULL; area = area->GetParent())
|
||
|
{
|
||
|
--count;
|
||
|
m_path[ count ].area = area;
|
||
|
m_path[ count ].how = area->GetParentHow();
|
||
|
}
|
||
|
|
||
|
// compute path positions
|
||
|
if (ComputePathPositions() == false)
|
||
|
{
|
||
|
PrintIfWatched("Error building path\n");
|
||
|
DestroyPath();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (goal == NULL)
|
||
|
{
|
||
|
switch (m_path[m_pathLength - 1].how)
|
||
|
{
|
||
|
case GO_NORTH:
|
||
|
case GO_SOUTH:
|
||
|
pathEndPosition.x = m_path[m_pathLength - 1].pos.x;
|
||
|
pathEndPosition.y = effectiveGoalArea->GetCenter()->y;
|
||
|
break;
|
||
|
|
||
|
case GO_EAST:
|
||
|
case GO_WEST:
|
||
|
pathEndPosition.x = effectiveGoalArea->GetCenter()->x;
|
||
|
pathEndPosition.y = m_path[m_pathLength - 1].pos.y;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
GetGroundHeight(&pathEndPosition, &pathEndPosition.z);
|
||
|
}
|
||
|
|
||
|
// append path end position
|
||
|
m_path[ m_pathLength ].area = effectiveGoalArea;
|
||
|
m_path[ m_pathLength ].pos = pathEndPosition;
|
||
|
m_path[ m_pathLength ].ladder = NULL;
|
||
|
m_path[ m_pathLength ].how = NUM_TRAVERSE_TYPES;
|
||
|
++m_pathLength;
|
||
|
|
||
|
// do movement setup
|
||
|
m_pathIndex = 1;
|
||
|
m_areaEnteredTimestamp = gpGlobals->time;
|
||
|
m_spotEncounter = NULL;
|
||
|
m_goalPosition = m_path[1].pos;
|
||
|
|
||
|
if (m_path[1].ladder != NULL)
|
||
|
SetupLadderMovement();
|
||
|
else
|
||
|
m_pathLadder = NULL;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Return estimated distance left to travel along path
|
||
|
|
||
|
float CCSBot::GetPathDistanceRemaining() const
|
||
|
{
|
||
|
if (!HasPath())
|
||
|
return -1.0f;
|
||
|
|
||
|
int idx = (m_pathIndex < m_pathLength) ? m_pathIndex : m_pathLength - 1;
|
||
|
|
||
|
float dist = 0.0f;
|
||
|
const Vector *prevCenter = m_path[m_pathIndex].area->GetCenter();
|
||
|
|
||
|
for (int i = idx + 1; i < m_pathLength; ++i)
|
||
|
{
|
||
|
dist += (*m_path[i].area->GetCenter() - *prevCenter).Length();
|
||
|
prevCenter = m_path[i].area->GetCenter();
|
||
|
}
|
||
|
|
||
|
return dist;
|
||
|
}
|
||
|
|
||
|
// Draw a portion of our current path for debugging.
|
||
|
|
||
|
void CCSBot::DrawPath()
|
||
|
{
|
||
|
if (!HasPath())
|
||
|
return;
|
||
|
|
||
|
for (int i = 1; i < m_pathLength; ++i)
|
||
|
{
|
||
|
UTIL_DrawBeamPoints(m_path[i - 1].pos, m_path[i].pos, 2, 255, 75, 0);
|
||
|
}
|
||
|
|
||
|
Vector close;
|
||
|
if (FindOurPositionOnPath(&close, true) >= 0)
|
||
|
{
|
||
|
UTIL_DrawBeamPoints(close + Vector(0, 0, 25), close, 1, 0, 255, 0);
|
||
|
UTIL_DrawBeamPoints(close + Vector(25, 0, 0), close + Vector(-25, 0, 0), 1, 0, 255, 0);
|
||
|
UTIL_DrawBeamPoints(close + Vector(0, 25, 0), close + Vector(0, -25, 0), 1, 0, 255, 0);
|
||
|
}
|
||
|
}
|