mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-28 15:45:41 +03:00
511 lines
13 KiB
C++
511 lines
13 KiB
C++
/*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* In addition, as a special exception, the author gives permission to
|
|
* link the code of this program with the Half-Life Game Engine ("HL
|
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
|
* respects for all of the code used other than the HL Engine and MODs
|
|
* from Valve. If you modify this file, you may extend this exception
|
|
* to your version of the file, but you are not obligated to do so. If
|
|
* you do not wish to do so, delete this exception statement from your
|
|
* version.
|
|
*
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
const float updateTimesliceDuration = 0.5f;
|
|
|
|
int _navAreaCount = 0;
|
|
int _currentIndex = 0;
|
|
|
|
inline CNavNode *LadderEndSearch(CBaseEntity *pEntity, 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 dir = (-1); dir < 2 * NUM_DIRECTIONS; dir++)
|
|
{
|
|
Vector tryPos = center;
|
|
|
|
if (dir >= NUM_DIRECTIONS)
|
|
AddDirectionVector(&tryPos, (NavDirType)(dir - NUM_DIRECTIONS), GenerationStepSize * 2.0f);
|
|
else if (dir >= 0)
|
|
AddDirectionVector(&tryPos, (NavDirType)dir, 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(pEntity->pev), &result);
|
|
|
|
if (result.flFraction != 1.0f
|
|
#ifdef REGAMEDLL_FIXES
|
|
|| result.fStartSolid
|
|
#endif
|
|
)
|
|
continue;
|
|
|
|
// if no node exists here, create one and continue the search
|
|
if (!CNavNode::GetNode(&tryPos))
|
|
{
|
|
return new CNavNode(&tryPos, &tryNormal, nullptr);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 (Q_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)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
|
WRITE_BYTE(BOT_PROGGRESS_DRAW);
|
|
WRITE_BYTE(int(progress * 100.0f));
|
|
WRITE_STRING(title);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void startProgressMeter(const char *title)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
|
WRITE_BYTE(BOT_PROGGRESS_START);
|
|
WRITE_STRING(title);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void hideProgressMeter()
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgBotProgress);
|
|
WRITE_BYTE(BOT_PROGGRESS_HIDE);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void CCSBot::StartLearnProcess()
|
|
{
|
|
startProgressMeter("#CZero_LearningMap");
|
|
drawProgressMeter(0, "#CZero_LearningMap");
|
|
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)
|
|
{
|
|
// search is exhausted - continue search from ends of ladders
|
|
for (auto ladder : TheNavLadderList)
|
|
{
|
|
// 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)
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
#ifdef REGAMEDLL_FIXES
|
|
// if we're incrementally generating, don't overlap existing nav areas
|
|
CNavArea *overlap = TheNavAreaGrid.GetNavArea(&to, HumanHeight);
|
|
if (overlap)
|
|
{
|
|
walkable = false;
|
|
}
|
|
#endif
|
|
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.begin();
|
|
|
|
_navAreaCount = TheNavAreaList.size();
|
|
_currentIndex = 0;
|
|
|
|
ApproachAreaAnalysisPrep();
|
|
DestroyHidingSpots();
|
|
|
|
startProgressMeter("#CZero_AnalyzingHidingSpots");
|
|
drawProgressMeter(0, "#CZero_AnalyzingHidingSpots");
|
|
}
|
|
|
|
bool CCSBot::AnalyzeAlphaStep()
|
|
{
|
|
_currentIndex++;
|
|
if (m_analyzeIter == TheNavAreaList.end())
|
|
return false;
|
|
|
|
CNavArea *area = (*m_analyzeIter);
|
|
area->ComputeHidingSpots();
|
|
area->ComputeApproachAreas();
|
|
m_analyzeIter++;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCSBot::UpdateAnalyzeAlphaProcess()
|
|
{
|
|
float startTime = g_engfuncs.pfnTime();
|
|
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
|
|
{
|
|
if (AnalyzeAlphaStep() == false)
|
|
{
|
|
drawProgressMeter(0.5f, "#CZero_AnalyzingHidingSpots");
|
|
CleanupApproachAreaAnalysisPrep();
|
|
StartAnalyzeBetaProcess();
|
|
return;
|
|
}
|
|
}
|
|
|
|
float progress = (double(_currentIndex) / double(_navAreaCount)) * 0.5f;
|
|
drawProgressMeter(progress, "#CZero_AnalyzingHidingSpots");
|
|
}
|
|
|
|
void CCSBot::StartAnalyzeBetaProcess()
|
|
{
|
|
m_processMode = PROCESS_ANALYZE_BETA;
|
|
m_analyzeIter = TheNavAreaList.begin();
|
|
|
|
_navAreaCount = TheNavAreaList.size();
|
|
_currentIndex = 0;
|
|
}
|
|
|
|
bool CCSBot::AnalyzeBetaStep()
|
|
{
|
|
_currentIndex++;
|
|
if (m_analyzeIter == TheNavAreaList.end())
|
|
return false;
|
|
|
|
CNavArea *area = (*m_analyzeIter);
|
|
area->ComputeSpotEncounters();
|
|
area->ComputeSniperSpots();
|
|
m_analyzeIter++;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCSBot::UpdateAnalyzeBetaProcess()
|
|
{
|
|
float startTime = g_engfuncs.pfnTime();
|
|
while (g_engfuncs.pfnTime() - startTime < updateTimesliceDuration)
|
|
{
|
|
if (AnalyzeBetaStep() == false)
|
|
{
|
|
drawProgressMeter(1, "#CZero_AnalyzingApproachPoints");
|
|
StartSaveProcess();
|
|
return;
|
|
}
|
|
}
|
|
|
|
float progress = (double(_currentIndex) / double(_navAreaCount) + 1.0f) * 0.5f;
|
|
drawProgressMeter(progress, "#CZero_AnalyzingApproachPoints");
|
|
}
|
|
|
|
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();
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
Q_sprintf(cmd, "map %s\n", STRING(gpGlobals->mapname));
|
|
#else
|
|
Q_snprintf(cmd, sizeof(cmd), "changelevel %s\n", STRING(gpGlobals->mapname));
|
|
#endif
|
|
|
|
SERVER_COMMAND(cmd);
|
|
}
|
|
|
|
void CCSBot::StartNormalProcess()
|
|
{
|
|
m_processMode = PROCESS_NORMAL;
|
|
}
|