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.
481 lines
11 KiB
481 lines
11 KiB
8 years ago
|
#include "bot_common.h"
|
||
|
|
||
|
const float updateTimesliceDuration = 0.5f;
|
||
|
|
||
|
int _navAreaCount = 0;
|
||
|
int _currentIndex = 0;
|
||
|
|
||
|
inline CNavNode *LadderEndSearch(CBaseEntity *entity, const Vector *pos, NavDirType mountDir)
|
||
|
{
|
||
|
Vector center = *pos;
|
||
|
AddDirectionVector(¢er, mountDir, HalfHumanWidth);
|
||
|
|
||
|
// Test the ladder dismount point first, then each cardinal direction one and two steps away
|
||
|
|
||
|
for (int d = (-1); d < 2 * NUM_DIRECTIONS; ++d)
|
||
|
{
|
||
|
Vector tryPos = center;
|
||
|
|
||
|
if (d >= NUM_DIRECTIONS)
|
||
|
AddDirectionVector(&tryPos, (NavDirType)(d - NUM_DIRECTIONS), GenerationStepSize * 2.0f);
|
||
|
else if (d >= 0)
|
||
|
AddDirectionVector(&tryPos, (NavDirType)d, GenerationStepSize);
|
||
|
|
||
|
// step up a rung, to ensure adjacent floors are below us
|
||
|
tryPos.z += GenerationStepSize;
|
||
|
SnapToGrid(&tryPos);
|
||
|
|
||
|
// adjust height to account for sloping areas
|
||
|
Vector tryNormal;
|
||
|
if (GetGroundHeight(&tryPos, &tryPos.z, &tryNormal) == false)
|
||
|
continue;
|
||
|
|
||
|
// make sure this point is not on the other side of a wall
|
||
|
const float fudge = 2.0f;
|
||
|
TraceResult result;
|
||
|
UTIL_TraceLine(center + Vector(0, 0, fudge), tryPos + Vector(0, 0, fudge), ignore_monsters, dont_ignore_glass, ENT(entity->pev), &result);
|
||
|
|
||
|
if (result.flFraction != 1.0f || result.fStartSolid)
|
||
|
continue;
|
||
|
|
||
|
// if no node exists here, create one and continue the search
|
||
|
if (CNavNode::GetNode(&tryPos) == NULL)
|
||
|
{
|
||
|
return new CNavNode(&tryPos, &tryNormal, NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
CNavNode *CCSBot::AddNode(const Vector *destPos, const Vector *normal, NavDirType dir, CNavNode *source)
|
||
|
{
|
||
|
// check if a node exists at this location
|
||
|
CNavNode *node = const_cast<CNavNode *>(CNavNode::GetNode(destPos));
|
||
|
|
||
|
// if no node exists, create one
|
||
|
bool useNew = false;
|
||
|
if (node == NULL)
|
||
|
{
|
||
|
node = new CNavNode(destPos, normal, source);
|
||
|
useNew = true;
|
||
|
}
|
||
|
|
||
|
// connect source node to new node
|
||
|
source->ConnectTo(node, dir);
|
||
|
|
||
|
// optimization: if deltaZ changes very little, assume connection is commutative
|
||
|
const float zTolerance = 10.0f; // 50.0f;
|
||
|
if (fabs(source->GetPosition()->z - destPos->z) < zTolerance)
|
||
|
{
|
||
|
node->ConnectTo(source, OppositeDirection(dir));
|
||
|
node->MarkAsVisited(OppositeDirection(dir));
|
||
|
}
|
||
|
|
||
|
if (useNew)
|
||
|
{
|
||
|
// new node becomes current node
|
||
|
m_currentNode = node;
|
||
|
}
|
||
|
|
||
|
Vector ceiling;
|
||
|
Vector floor;
|
||
|
TraceResult result;
|
||
|
|
||
|
bool crouch = false;
|
||
|
const float epsilon = 0.1f;
|
||
|
|
||
|
for (float y = -16.0f; y <= 16.0f + epsilon; y += 16.0f)
|
||
|
{
|
||
|
for (float x = -16.0f; x <= 16.0f + epsilon; x += 16.0f)
|
||
|
{
|
||
|
floor = *destPos + Vector(x, y, 5.0f);
|
||
|
ceiling = *destPos + Vector(x, y, 72.0f - epsilon);
|
||
|
|
||
|
UTIL_TraceLine(floor, ceiling, ignore_monsters, dont_ignore_glass, ENT(pev), &result);
|
||
|
|
||
|
if (result.flFraction != 1.0f)
|
||
|
{
|
||
|
crouch = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (crouch)
|
||
|
{
|
||
|
node->SetAttributes(NAV_CROUCH);
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return node;
|
||
|
}
|
||
|
|
||
|
void drawProgressMeter(float progress, char *title)
|
||
|
{
|
||
|
#if 0
|
||
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
||
|
WRITE_BYTE(FLAG_PROGRESS_DRAW);
|
||
|
WRITE_BYTE((int)progress);
|
||
|
WRITE_STRING(title);
|
||
|
MESSAGE_END();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void startProgressMeter(const char *title)
|
||
|
{
|
||
|
#if 0
|
||
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
||
|
WRITE_BYTE(FLAG_PROGRESS_START);
|
||
|
WRITE_STRING(title);
|
||
|
MESSAGE_END();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void hideProgressMeter()
|
||
|
{
|
||
|
#if 0
|
||
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
||
|
WRITE_BYTE(FLAG_PROGRESS_HIDE);
|
||
|
MESSAGE_END();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void CCSBot::StartLearnProcess()
|
||
|
{
|
||
|
startProgressMeter("Analyzing map geometry...");
|
||
|
drawProgressMeter(0, "Analyzing map geometry...");
|
||
|
BuildLadders();
|
||
|
|
||
|
Vector normal;
|
||
|
Vector pos = pev->origin;
|
||
|
|
||
|
SnapToGrid(&pos.x);
|
||
|
SnapToGrid(&pos.y);
|
||
|
|
||
|
if (!GetGroundHeight(&pos, &pos.z, &normal))
|
||
|
{
|
||
|
CONSOLE_ECHO("ERROR: Start position invalid\n\n");
|
||
|
m_processMode = PROCESS_NORMAL;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_currentNode = new CNavNode(&pos, &normal);
|
||
|
m_goalPosition = pev->origin;
|
||
|
m_processMode = PROCESS_LEARN;
|
||
|
}
|
||
|
|
||
|
// Search the world and build a map of possible movements.
|
||
|
// The algorithm begins at the bot's current location, and does a recursive search
|
||
|
// outwards, tracking all valid steps and generating a directed graph of CNavNodes.
|
||
|
// Sample the map one "step" in a cardinal direction to learn the map.
|
||
|
// Returns true if sampling needs to continue, or false if done.
|
||
|
|
||
|
bool CCSBot::LearnStep()
|
||
|
{
|
||
|
// take a step
|
||
|
while (true)
|
||
|
{
|
||
|
if (m_currentNode == NULL)
|
||
|
{
|
||
|
// search is exhausted - continue search from ends of ladders
|
||
|
FOR_EACH_LL (TheNavLadderList, it)
|
||
|
{
|
||
|
CNavLadder *ladder = TheNavLadderList[it];
|
||
|
|
||
|
// check ladder bottom
|
||
|
if ((m_currentNode = LadderEndSearch(ladder->m_entity, &ladder->m_bottom, ladder->m_dir)) != 0)
|
||
|
break;
|
||
|
|
||
|
// check ladder top
|
||
|
if ((m_currentNode = LadderEndSearch(ladder->m_entity, &ladder->m_top, ladder->m_dir)) != 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (m_currentNode == NULL)
|
||
|
{
|
||
|
// all seeds exhausted, sampling complete
|
||
|
GenerateNavigationAreaMesh();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Take a step from this node
|
||
|
for (int dir = NORTH; dir < NUM_DIRECTIONS; dir++)
|
||
|
{
|
||
|
if (!m_currentNode->HasVisited((NavDirType)dir))
|
||
|
{
|
||
|
float feetOffset = pev->origin.z - GetFeetZ();
|
||
|
|
||
|
// start at current node position
|
||
|
Vector pos = *m_currentNode->GetPosition();
|
||
|
|
||
|
// snap to grid
|
||
|
int cx = SnapToGrid(pos.x);
|
||
|
int cy = SnapToGrid(pos.y);
|
||
|
|
||
|
// attempt to move to adjacent node
|
||
|
switch (dir)
|
||
|
{
|
||
|
case NORTH: cy -= GenerationStepSize; break;
|
||
|
case SOUTH: cy += GenerationStepSize; break;
|
||
|
case EAST: cx += GenerationStepSize; break;
|
||
|
case WEST: cx -= GenerationStepSize; break;
|
||
|
}
|
||
|
|
||
|
pos.x = cx;
|
||
|
pos.y = cy;
|
||
|
|
||
|
m_generationDir = (NavDirType)dir;
|
||
|
|
||
|
// mark direction as visited
|
||
|
m_currentNode->MarkAsVisited(m_generationDir);
|
||
|
|
||
|
// test if we can move to new position
|
||
|
TraceResult result;
|
||
|
Vector from, to;
|
||
|
|
||
|
// modify position to account for change in ground level during step
|
||
|
to.x = pos.x;
|
||
|
to.y = pos.y;
|
||
|
Vector toNormal;
|
||
|
if (GetGroundHeight(&pos, &to.z, &toNormal) == false)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
from = *m_currentNode->GetPosition();
|
||
|
|
||
|
Vector fromOrigin = from + Vector(0, 0, feetOffset);
|
||
|
Vector toOrigin = to + Vector(0, 0, feetOffset);
|
||
|
|
||
|
UTIL_SetOrigin(pev, toOrigin);
|
||
|
UTIL_TraceLine(fromOrigin, toOrigin, ignore_monsters, dont_ignore_glass, ENT(pev), &result);
|
||
|
|
||
|
bool walkable;
|
||
|
|
||
|
if (result.flFraction == 1.0f && !result.fStartSolid)
|
||
|
{
|
||
|
// the trace didnt hit anything - clear
|
||
|
float toGround = to.z;
|
||
|
float fromGround = from.z;
|
||
|
|
||
|
float epsilon = 0.1f;
|
||
|
|
||
|
// check if ledge is too high to reach or will cause us to fall to our death
|
||
|
if (toGround - fromGround > JumpCrouchHeight + epsilon || fromGround - toGround > DeathDrop)
|
||
|
{
|
||
|
walkable = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// check surface normals along this step to see if we would cross any impassable slopes
|
||
|
Vector delta = to - from;
|
||
|
const float inc = 2.0f;
|
||
|
float along = inc;
|
||
|
bool done = false;
|
||
|
float ground;
|
||
|
Vector normal;
|
||
|
|
||
|
walkable = true;
|
||
|
|
||
|
while (!done)
|
||
|
{
|
||
|
Vector p;
|
||
|
|
||
|
// need to guarantee that we test the exact edges
|
||
|
if (along >= GenerationStepSize)
|
||
|
{
|
||
|
p = to;
|
||
|
done = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p = from + delta * (along / GenerationStepSize);
|
||
|
}
|
||
|
|
||
|
if (GetGroundHeight(&p, &ground, &normal) == false)
|
||
|
{
|
||
|
walkable = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// check for maximum allowed slope
|
||
|
if (normal.z < 0.7f)
|
||
|
{
|
||
|
walkable = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
along += inc;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// TraceLine hit something...
|
||
|
else
|
||
|
{
|
||
|
if (IsEntityWalkable(VARS(result.pHit), WALK_THRU_EVERYTHING))
|
||
|
{
|
||
|
walkable = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
walkable = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we're incrementally generating, don't overlap existing nav areas
|
||
|
CNavArea *overlap = TheNavAreaGrid.GetNavArea(&to, HumanHeight);
|
||
|
if (overlap != NULL)
|
||
|
{
|
||
|
walkable = false;
|
||
|
}
|
||
|
if (walkable)
|
||
|
{
|
||
|
// we can move here
|
||
|
// create a new navigation node, and update current node pointer
|
||
|
CNavNode *newNode = AddNode(&to, &toNormal, m_generationDir, m_currentNode);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// all directions have been searched from this node - pop back to its parent and continue
|
||
|
m_currentNode = m_currentNode->GetParent();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CCSBot::UpdateLearnProcess()
|
||
|
{
|
||
|
float startTime = g_engfuncs.pfnTime();
|
||
|
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
|
||
|
{
|
||
|
if (LearnStep() == false)
|
||
|
{
|
||
|
StartAnalyzeAlphaProcess();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CCSBot::StartAnalyzeAlphaProcess()
|
||
|
{
|
||
|
m_processMode = PROCESS_ANALYZE_ALPHA;
|
||
|
m_analyzeIter = TheNavAreaList.Head ();
|
||
|
|
||
|
_navAreaCount = TheNavAreaList.Count();
|
||
|
_currentIndex = 0;
|
||
|
|
||
|
DestroyHidingSpots();
|
||
|
|
||
|
startProgressMeter("Analyzing hiding spots...");
|
||
|
drawProgressMeter(0, "Analyzing hiding spots...");
|
||
|
}
|
||
|
|
||
|
bool CCSBot::AnalyzeAlphaStep()
|
||
|
{
|
||
|
++_currentIndex;
|
||
|
if (m_analyzeIter == TheNavAreaList.InvalidIndex ())
|
||
|
return false;
|
||
|
|
||
|
CNavArea *area = TheNavAreaList.Element (m_analyzeIter);
|
||
|
area->ComputeHidingSpots();
|
||
|
area->ComputeApproachAreas();
|
||
|
m_analyzeIter = TheNavAreaList.Next (m_analyzeIter);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CCSBot::UpdateAnalyzeAlphaProcess()
|
||
|
{
|
||
|
float startTime = g_engfuncs.pfnTime();
|
||
|
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
|
||
|
{
|
||
|
if (AnalyzeAlphaStep() == false)
|
||
|
{
|
||
|
drawProgressMeter(50, "Analyzing hiding spots...");
|
||
|
StartAnalyzeBetaProcess();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float progress = (double (_currentIndex) / double (_navAreaCount)) * 0.5f;
|
||
|
drawProgressMeter(progress, "Analyzing hiding spots...");
|
||
|
}
|
||
|
|
||
|
void CCSBot::StartAnalyzeBetaProcess()
|
||
|
{
|
||
|
m_processMode = PROCESS_ANALYZE_BETA;
|
||
|
m_analyzeIter = TheNavAreaList.Head ();
|
||
|
|
||
|
_navAreaCount = TheNavAreaList.Count ();
|
||
|
_currentIndex = 0;
|
||
|
}
|
||
|
|
||
|
bool CCSBot::AnalyzeBetaStep()
|
||
|
{
|
||
|
++_currentIndex;
|
||
|
if (m_analyzeIter == TheNavAreaList.InvalidIndex ())
|
||
|
return false;
|
||
|
|
||
|
CNavArea *area = TheNavAreaList.Element (m_analyzeIter);
|
||
|
area->ComputeSpotEncounters();
|
||
|
area->ComputeSniperSpots();
|
||
|
m_analyzeIter = TheNavAreaList.Next (m_analyzeIter);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void CCSBot::UpdateAnalyzeBetaProcess()
|
||
|
{
|
||
|
float startTime = g_engfuncs.pfnTime();
|
||
|
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
|
||
|
{
|
||
|
if (AnalyzeBetaStep() == false)
|
||
|
{
|
||
|
drawProgressMeter(100, "Analyzing approach points...");
|
||
|
StartSaveProcess();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float progress = (double (_currentIndex) / double (_navAreaCount) + 1.0f) * 0.5f;
|
||
|
drawProgressMeter(progress, "Analyzing approach points...");
|
||
|
}
|
||
|
|
||
|
void CCSBot::StartSaveProcess()
|
||
|
{
|
||
|
m_processMode = PROCESS_SAVE;
|
||
|
}
|
||
|
|
||
|
void CCSBot::UpdateSaveProcess()
|
||
|
{
|
||
|
char filename[256];
|
||
|
char msg[256];
|
||
|
char cmd[128];
|
||
|
|
||
|
GET_GAME_DIR(filename);
|
||
|
|
||
|
Q_strcat(filename, "\\");
|
||
|
Q_strcat(filename, TheBots->GetNavMapFilename());
|
||
|
|
||
|
HintMessageToAllPlayers("Saving...");
|
||
|
SaveNavigationMap(filename);
|
||
|
|
||
|
Q_sprintf(msg, "Navigation file '%s' saved.", filename);
|
||
|
HintMessageToAllPlayers(msg);
|
||
|
|
||
|
hideProgressMeter();
|
||
|
StartNormalProcess();
|
||
|
|
||
|
Q_sprintf (cmd, "changelevel %s\n", STRING (gpGlobals->mapname));
|
||
|
SERVER_COMMAND(cmd);
|
||
|
}
|
||
|
|
||
|
void CCSBot::StartNormalProcess()
|
||
|
{
|
||
|
m_processMode = PROCESS_NORMAL;
|
||
|
}
|